diff --git a/.changeset/15196-core-security-catalog-read.md b/.changeset/15196-core-security-catalog-read.md deleted file mode 100644 index 64c69aafb9a..00000000000 --- a/.changeset/15196-core-security-catalog-read.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@objectstack/core": minor ---- - -feat(core): one by-name read of the security catalog (`createSecurityCatalogReader`) - -Clause-②: yes (widening) - -- **What is new.** `createSecurityCatalogReader({ registry, metadata })` returns a reader with two members: `resolve(type, name)`, the definition a position, permission set or capability name resolves to (or `undefined`), and `list(type)`, one entry per name. `type` is `'position' | 'permission' | 'capability'`. Each entry is `{ type, name, definition, source, packageId? }`. The types `SecurityCatalogType`, `SecurityCatalogSourceName`, `SecurityCatalogRegistry`, `SecurityCatalogMetadataService`, `SecurityCatalogSources`, `SecurityCatalogEntry` and `SecurityCatalogReader` are exported with it. -- **Where it reads.** ObjectQL's `SchemaRegistry` (`engine.registry`) first, then the kernel `metadata` service for the names the registry does not hold. Neither holds the whole catalog: the engine registry carries the platform's own permission sets and every package manifest's catalog items but no stack-declared position, and the metadata service carries the stack-declared positions but not the platform's permission sets. Both are required; construction refuses a missing one. -- **A name two packages ship** resolves the way the registry's by-name read does today: a stored override first, else the first-registered package's body. -- **What it does not answer.** Whether an item is in effect: the row `active` flag stays the authority, and no definition carries it. The position → permission-set binding. Organization scope: the catalog is environment-level. -- **Failures are loud.** A reader that throws, or a metadata read that lost a loader and found nothing, raises `AuthzStoreUnavailableError` (`SERVICE_UNAVAILABLE`, 503) instead of answering "no such item". A definition owned by a disabled package answers neither member. -- Nothing calls the reader yet; no grant changes. diff --git a/.changeset/15196-grant-permission-set-name-column.md b/.changeset/15196-grant-permission-set-name-column.md deleted file mode 100644 index ff442737d74..00000000000 --- a/.changeset/15196-grant-permission-set-name-column.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@objectstack/plugin-security": minor -"@objectstack/plugin-auth": minor -"@objectstack/verify": minor ---- - -`sys_user_permission_set` gains `permission_set`, the name of the permission set a grant holds, written beside `permission_set_id` (ADR-0131 D4) - -Clause-②: yes (widening) - -- **The column.** `permission_set` is a read-only text column, at most 100 characters, holding the `name` of the `sys_permission_set` row that `permission_set_id` points at. It is readable everywhere the grant row is readable. A grant written before this release has `NULL` here until the backfill stage rewrites it. No reader uses the column yet: the grant is still resolved from `permission_set_id`, which stays until it is dropped in a later major (ADR-0131 D10). -- **The platform writes it, on every write that carries `permission_set_id`, for every caller.** Two `@objectstack/plugin-security` engine hooks (`beforeInsert` and `beforeUpdate` on `sys_user_permission_set`) look up the set by id and store its name. A write that sends only the id, which is how the data door and the Setup forms write, gets the name filled in. -- **A name that names a different set is refused** with `400 VALIDATION_FAILED`, `invalid_value` at `permission_set`. This covers a name that disagrees with the id written beside it, or with the id already stored when only the name is written. For a non-system caller it also covers a name beside an id that names no set this caller's organization can see. A name that agrees is accepted. A cleared name (`null`) is not stored as a clear: the derived name is written back. Before this change the column did not exist, so a write naming it was refused with `400 INVALID_FIELD`. No write that was accepted before is refused now. -- **Every platform grant writer writes both columns:** the organization-admin reconcile and the platform-admin promotion in `@objectstack/plugin-security`, the self-registration grant in `@objectstack/plugin-auth`, and the RLS probe persona in `@objectstack/verify`. -- **Nothing to migrate.** No principal's grants change. To fill the column on grants written by your own code, write the set's name as `permission_set`, or leave it out and the platform fills it in. Do not write any other value there. diff --git a/.changeset/15207-deployment-plumbing-no-organization-column.md b/.changeset/15207-deployment-plumbing-no-organization-column.md deleted file mode 100644 index 126fbaef36f..00000000000 --- a/.changeset/15207-deployment-plumbing-no-organization-column.md +++ /dev/null @@ -1,28 +0,0 @@ ---- -'@objectstack/platform-objects': minor -'@objectstack/service-automation': minor -'@objectstack/service-realtime': minor -'@objectstack/spec': minor ---- - -feat(platform-objects,service-automation,service-realtime)!: seven deployment-level platform tables lose their injected organization column, and reading them needs `manage_platform_settings` (ADR-0131 D7) - -Clause-②: no (narrowing) - - - -**BREAKING**, shipped as `minor` under the repo's launch-window convention for breaking changes (Changesets pre mode is not on yet). - -`sys_job`, `sys_job_run`, `sys_job_queue`, `sys_flow_dispatch`, `sys_migration`, `sys_migration_journal` and `sys_presence` hold deployment-level state. No writer attributes a row of any of them to an organization: every write is a system-context write whose row names none, and nothing writes `sys_presence` through ObjectQL at all. So the injected `organization_id` column only ever held NULL. ADR-0131 D7 takes it off: each object now declares `systemFields: { tenant: false }`. - -With no column there is no tenant wall, so these tables are governed by object permission. Each also declares `requiredPermissions: ['manage_platform_settings']`. Without that gate, a walled deployment's `organization_admin`, whose grant carries the superuser bits on every object, would read every other organization's job errors, queued payloads, dispatch keys and migration traces. - -**What moves for consumers.** - -- **The column.** `organization_id` is no longer a field of these seven objects. A filter, list-view column, report grouping, formula or seed key naming it on one of them is now an unknown field. Delete the reference: no organization owns a row of these tables. -- **Who reads, on a walled posture** (`group` or `isolated`). Before: the wall compared the NULL column to the caller's organization, so every reader got zero rows, platform administrators included (unless the deployment declared the table platform-global, which stood the wall down). Now: a principal holding `manage_platform_settings` (platform administrators hold it) lists every row; anyone else is refused `403 PERMISSION_DENIED`. -- **Who reads, on the `single` posture.** Before: any principal with a read grant on the object read every row, an organization administrator included. Now: only a principal holding `manage_platform_settings` reads; an organization administrator who is not a platform administrator is refused `403 PERMISSION_DENIED`. Grant the capability to an operator who needs these tables. - -**Unchanged.** Every platform writer and reader of these tables uses a system context, which no capability gate applies to, so job scheduling, the queue, flow dispatch, migration flags and the migration journal behave as before. The physical unique indexes are unchanged: none of these objects declares an organization-scoped one. - -**Existing databases.** Schema sync only adds, so the physical `organization_id` column stays on each existing table (with its index, where the deployment indexed it), and the boot drift report names it orphaned. By the writer census it holds only NULL, so dropping it loses nothing: `os migrate apply --allow-destructive` drops it, the remedy the drift report names. diff --git a/.changeset/15207-lifecycle-no-tenant-column-no-partition.md b/.changeset/15207-lifecycle-no-tenant-column-no-partition.md deleted file mode 100644 index 6a413a92f32..00000000000 --- a/.changeset/15207-lifecycle-no-tenant-column-no-partition.md +++ /dev/null @@ -1,9 +0,0 @@ ---- -'@objectstack/objectql': patch ---- - -fix(objectql): the lifecycle reaper and archiver no longer partition an object with no tenant column by organization - -A tenant-scope `lifecycle.retention_overrides` entry gives one organization its own retention window, and the reaper and the archiver apply it by partitioning the object's rows on `organization_id`: one pass for that organization's rows, then a global pass for everyone else's. On an object that has no `organization_id` column — one declaring `systemFields: { tenant: false }`, such as the deployment-level platform tables (`sys_job`, `sys_job_run`, `sys_job_queue`, `sys_flow_dispatch`, `sys_migration`, `sys_migration_journal`, `sys_presence`), or any other object the registry injects no tenant column into and whose author declares none — both passes named a column the table does not have. The SQL driver refused them (`INVALID_FILTER`), the sweep reported the object in its errors, and the table's retention stopped. - -Such an object now has no tenant partition, the answer a federated object already got: the sweep runs its one global pass at the global window. No row of it belongs to an organization, so a tenant override naming it has nothing to select, and it is not applied. An object that has the column keeps its per-tenant windows unchanged. diff --git a/.changeset/20471-component-row-anchor-quotes.md b/.changeset/20471-component-row-anchor-quotes.md deleted file mode 100644 index 0b337c0f465..00000000000 --- a/.changeset/20471-component-row-anchor-quotes.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -Three `ComponentPropsMap` read-point records now quote the objectui line they cite - -Clause-②: no - -The docblocks of `action:button`, `action:icon` and `element:definition-list` in -`src/ui/component.zod.ts` each quote the first line of the row's props-read site, re-read -against objectui at the pin this package builds against (`a58626c88`): the runner-forward -literal of the two action blocks, and the `readProps` call of the definition list. A pin bump -that moves one of those lines, or changes it, now fails `check:objectui-pin-citations` and -names where the quoted line went, where before only the cited sha was checked. The other -three rows of that section (`action:group`, `action:menu`, `element:repeater`) carry no quote -yet. Comment text only: no schema, key, type or export changes. diff --git a/.changeset/20471-component-row-anchor-requote.md b/.changeset/20471-component-row-anchor-requote.md deleted file mode 100644 index 5a196de2cca..00000000000 --- a/.changeset/20471-component-row-anchor-requote.md +++ /dev/null @@ -1,19 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -The `action:group`, `action:menu` and `element:repeater` read-point records are re-measured at the objectui pin and quote the line they cite - -Clause-②: no - -The docblocks of `action:group`, `action:menu` and `element:repeater` in `src/ui/component.zod.ts` -cited objectui lines that had moved without any gate noticing: the two containers' anchors by 3 to 8 -lines since objectui#11638, every repeater anchor by 28 lines since objectui#11168 slice 2, and the -repeater's `data-objectstack` filter and sort anchors since earlier pins. Each is now re-pointed at -the pin this package builds against (`a58626c88`), as are `action:button`'s `static-params.ts` -citation and the `element:definition-list` registration notes, which said the registration -publishes the strings `'1'` / `'2'` and marks `items` required (it no longer does either). Each of -the three rows now quotes the first line of its props-read site (the member forward of the two -containers, the `readProps` call of the repeater), so a pin bump that moves or changes one of those -lines fails `check:objectui-pin-citations`. All six rows of that section now carry a quote. Comment -text only: no schema, key, type or export changes. diff --git a/.changeset/21886-add-member-platform-admin-visibility.md b/.changeset/21886-add-member-platform-admin-visibility.md deleted file mode 100644 index 4bf3beef56b..00000000000 --- a/.changeset/21886-add-member-platform-admin-visibility.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@objectstack/platform-objects": patch ---- - -"Add Member" is offered only to a platform administrator, the one standing its endpoint admits. - -Clause-②: no - -- `sys_member`'s `add_member` toolbar action now declares `visible: 'current_user.isPlatformAdmin == true'`. `requiresFeature: 'organization'` composes onto it at parse time, so the served predicate reads `(current_user.isPlatformAdmin == true) && features.organization != false`. -- Its endpoint, `POST /api/v1/auth/organization/add-member`, has always admitted a platform administrator alone (ADR-0068) and answered every other caller, org owners and admins included, with 403 `PERMISSION_DENIED`. Before this change the button was still shown to every member of the organization. -- ⛔ Nothing you author changes. The endpoint and the callers it admits are unchanged, and no key, export or parameter is added. The action's label is unchanged. diff --git a/.changeset/21898-flow-builtin-node-config-values-refused.md b/.changeset/21898-flow-builtin-node-config-values-refused.md deleted file mode 100644 index f5f8ab6f1cb..00000000000 --- a/.changeset/21898-flow-builtin-node-config-values-refused.md +++ /dev/null @@ -1,43 +0,0 @@ ---- -'@objectstack/spec': minor ---- - -A builtin flow node's `config` value that its executor contract refuses is refused at parse, with a location, in the contract's own words: `create_record` `outputVariable: 42`, a screen field `min: '1'`, a `get_record` `limit: '10'` and the like no longer pass the build doors and then fail every run. - -Clause-②: yes (narrowing) - - - -**BREAKING**: an accept-set narrowing on a published authoring surface, shipped as `minor` under the launch-window convention for accept-set narrowings. - -**Why.** Every builtin executor parses its node's `config` against the contract `getBuiltinNodeConfigContracts()` names before it acts, and refuses the node on any finding. The build doors judged only the keys that contract requires, left out, so a present value it refuses passed `FlowSchema.parse`, `objectstack validate` and `objectstack compile` (compile copied it into `dist/objectstack.json`), registered, and failed every run that reached the node: `create_record 'mk': config does not satisfy the create_record contract — config.outputVariable: Invalid input: expected string, received number`. - -**What is refused.** A node of any builtin type (`get_record`, `create_record`, `update_record`, `delete_record`, `notify`, `http`, `screen`, `script`, `subflow`, `map`, `loop`, `parallel`, `try_catch`), at any depth, whose present config value its executor contract refuses — a wrong type, a value outside the declared set or range, an empty `function` / `flowName`, or a rule finding on present keys (a `notify` `template` beside an inline `title`). The refusal is the existing closed-set code `node-config-refused-by-contract`, `params: { nodeType, key }`, anchored at the key (`nodes.N.config.outputVariable`, `nodes.N.config.fields.0.min`), from the one judge `flowNodeConfigRefusals` that `FlowSchema.parse`, `AutomationEngine.registerFlow` (which parses first) and `objectstack validate` share. The issue's `code` is `custom`. That covers `FlowSchema`, `defineFlow()`, `defineStack` (`STACK_SCHEMA_INVALID`, 422, at `flows.N.nodes.M.config.`), `os validate`, `os compile`, an artifact's parse, `registerFlow` and the metadata save door (`422 INVALID_METADATA`). - -**What the build doors still accept, byte for byte.** Every value its contract accepts, and the values this arm holds back: - -- a value carrying a `{token}` (also spelled with double braces or a leading `$`) — never refused at the build doors for its pre-interpolation type. That is not a promise it runs: only `http` interpolates its config before it parses, so only an `http` slot sees the token's resolved value. Every other builtin parses its config as authored, so a token in one of its number or boolean slots (`limit: '{n}'`, `maxIterations: '{cap}'`, a screen field `min: '{m}'`, `multi: '{bulk}'`) still fails at its first run, exactly as before — write a literal there; -- on `http`, any value with a token inside it, and `signingSecret` (the credential channel may supply it); -- a `loop` with no `body` (its executor does not parse it), and the region slots of `loop`, `parallel` and `try_catch`; -- an undeclared or retired key, a screen field's `visibleWhen` and a CRUD `fields` value — each keeps the judge it had. - -## FROM → TO - -| you wrote | write instead | -|:--|:--| -| `outputVariable: 42` | `outputVariable: 'taskId'` — the variable's name | -| a screen field `min: '1'`, `max: '10'` | `min: 1`, `max: 10` | -| `limit: '10'`, `maxIterations: '5'` (any number slot outside `http`) | `limit: 10`, `maxIterations: 5` — a literal number only: these executors parse the config as authored, so a `{token}` here passes the build and fails every run | -| `multi: 'true'`, a screen field `required: 'yes'` (any boolean slot outside `http`) | `multi: true`, `required: true` — a literal boolean only, for the same reason | -| `http` `timeoutMs: '5000'`, `durable: 'yes'` | `timeoutMs: 5000`, `durable: true` — or, on `http` alone, a sole-token template such as `timeoutMs: '{timeout}'`: `http` interpolates before it parses, so the token resolves to its value's type first | -| `severity: 'loud'`, `mode: 'view'` | one of the declared values (`'info'` / `'warning'` / `'critical'`; `'create'` / `'edit'`) | -| a `notify` with both `template` and `title` | one content path, as the refusal's sentence says | - -**The one-line fix: write the value the contract declares at the key the refusal names.** The runtime never ran such a node, so the fix changes nothing a working flow does. - -**Who is affected, measured.** At `833d57c9cf`, every builtin node `config` authored in this repository's examples, docs, skills and `packages/qa` fixtures (96 nodes), and every one in hotcrm at `4054ec2680` (138 nodes), parses under this arm. A second census at `d1c7d8d392` that also reads helper calls, same-file constants and assignments into a node config (1065 configs in this repository, 138 in hotcrm) found no other real writer; 64 configs here take a value from an import, a call or a spread that no static reading evaluates, and are not counted either way. The one real writer found to store a refused value is the Studio flow designer, which saved a screen field's Min / Max as strings until objectui `5ba255538a`. Deployed metadata, and other repositories, were not measured. Where such a node already sits in a stored flow, the whole flow is refused at registration: at boot it is skipped with a warn naming it, its trigger not armed, while the flows beside it register. - -### The kit - -- **The refusal.** The value half of the executor-contract arm of `flowNodeConfigRefusals` in `automation/flow-node-config-refusals.ts`; no new code joins `FLOW_SLOT_REFUSAL_CODES`, and `getBuiltinNodeConfigContracts()` keeps its 13 entries. -- **The ledger.** The D3 semantic entry `flow-builtin-node-config-values-refused` (protocol 18). No key is removed, so there is no tombstone, and there is no D2 conversion: the platform cannot know the value the author meant. diff --git a/.changeset/21903-platform-admin-affordance-visibility.md b/.changeset/21903-platform-admin-affordance-visibility.md deleted file mode 100644 index 1100cee5ef9..00000000000 --- a/.changeset/21903-platform-admin-affordance-visibility.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -"@objectstack/platform-objects": patch ---- - -The user, OAuth-application and SSO-provider actions whose endpoint admits only a platform administrator are now offered only to a platform administrator. - -Clause-②: no - -- These thirteen actions now declare `visible: 'current_user.isPlatformAdmin == true'`, composed with their existing terms: - - `sys_user`: `ban_user`, `unban_user`, `unlock_user`, `create_user`, `set_user_password`, `impersonate_user` and `set_user_manager`; - - `sys_oauth_application`: `disable_oauth_application` and `enable_oauth_application`; - - `sys_sso_provider`: `register_sso_provider`, `register_saml_provider`, `request_domain_verification` and `verify_domain`. -- Where an action also carries `requiresFeature`, the feature gate composes onto it at parse time. For example, `ban_user` now serves `(current_user.isPlatformAdmin == true) && features.admin == true`. -- Each endpoint (`/api/v1/auth/admin/*`) has always admitted a platform administrator alone (ADR-0068) and answered every other caller, org owners and admins included, with 403 `PERMISSION_DENIED`. Before this change the buttons were still shown to those callers. -- `create_oauth_application`, `rotate_client_secret`, `delete_oauth_application` and `delete_sso_provider` are unchanged: their endpoints authorize the signed-in user or the record's owner, not the platform administrator. -- ⛔ Nothing you author changes. The endpoints and the callers they admit are unchanged, and no key, export or parameter is added. The actions' labels are unchanged. diff --git a/.changeset/21908-by-id-producers-opt-in.md b/.changeset/21908-by-id-producers-opt-in.md deleted file mode 100644 index b1d6f8b1cae..00000000000 --- a/.changeset/21908-by-id-producers-opt-in.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/service-storage": patch -"@objectstack/service-messaging": patch ---- - -The storage store's by-id methods and the HTTP outbox's `redeliver` now pass the explicit system opt-in (`{ isSystem: true }`) on their data-engine calls. Until now they reached the engine with no principal and no opt-in, and the security middleware let that through only because of its principal-less hand-off. - -Clause-②: no - -- **service-storage.** `StorageMetadataStore.getFile`, `updateFile`, `deleteFile`, `getSession`, `updateSession` and `deleteSession` take the opt-in inside the store. Access stays by id, and the reads stay unscoped by organization, as before. On update and delete the acting organization still reaches the driver beside the opt-in, so a row stamped for another organization is still out of reach of these doors. The doors keep the authorization they already ran. -- **service-storage, the update payload.** `updateFile` and `updateSession` now send the caller's patch alone, where they used to send the whole row read back merged with it. The engine's read-only strip, which does not run for a system write, used to take `organization_id` and the four audit columns out of that row; now the store never sends them. The stored row is the same as before, and a column another writer changed between the read and the write is no longer reverted by it. -- **service-messaging.** `SqlHttpOutbox.redeliver` takes the opt-in on both of its reads and on its reset write. The caller's `tenantId` stays on every call as the driver-level scope, so a delivery in another organization is still not found. The reset write states `bypassTenantAudit: false`, so a redelivery from a caller with no organization is still reported by the driver's tenant audit. -- None of the gates the security middleware runs before its hand-off applies to these calls. ⛔ No new export on either package entry, and no new elevation API. diff --git a/.changeset/21908-principal-less-producers-final.md b/.changeset/21908-principal-less-producers-final.md deleted file mode 100644 index ee1db8afa59..00000000000 --- a/.changeset/21908-principal-less-producers-final.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@objectstack/service-messaging": patch -"@objectstack/service-storage": patch -"@objectstack/service-settings": patch -"@objectstack/metadata-protocol": patch ---- - -The remaining platform producers in these four packages now pass the explicit system opt-in (`{ isSystem: true }`) on their data-engine calls. Until now they reached the engine with no principal and no opt-in, and the security middleware let that through only because of its principal-less hand-off. - -Clause-②: no - -- **service-messaging, the inbox read state.** `listInbox` (and its unread total), the receipt read behind it, and mark-read / mark-all-read take the opt-in inside the service. Their scope is unchanged: every read of a user's rows is keyed on the user id the door derived from the session, the receipt a mark-read inserts is stamped with it, and the receipt it updates is one a user-keyed read returned. -- **service-messaging, `owner_of:` audiences.** The record read takes the opt-in, the same posture as the email lookup beside it. It reads only `id` and the owner fields, and only the owner id leaves the resolver. An `owner_of:` audience on an object whose sharing model is `private` now resolves its owner; before, it resolved to nobody. -- **service-messaging, the rest of the fan-out and the outboxes.** The `role:` and `team:` membership reads, the email and SMS recipient reads, the notification template read, the dedup lookup in `emit()`, and both outboxes' enqueue, ack and list. -- **service-storage.** `StorageMetadataStore.createFile` and `createSession` insert under the opt-in. The organization still reaches the driver beside it, so the stored organization is unchanged, and the file's `owner_id` is still the uploading user. -- **service-settings.** The `sys_secret` store the plugin builds (insert, get, update), and the read that verifies a rotation before the old secret is reaped. A store `update` now writes the `ciphertext` it is given; without a context the engine's read-only strip dropped it. No caller in this repository uses `update`. -- **metadata-protocol.** `SysMetadataRepository.getByHash`, `list`, `history` and the history replay of `watch()`. -- None of the gates the middleware runs before its hand-off applies to these calls. ⛔ No new export on any package entry, and no new elevation API. diff --git a/.changeset/21922-metadata-protocol-meta-read-declines-code-datasource-row.md b/.changeset/21922-metadata-protocol-meta-read-declines-code-datasource-row.md deleted file mode 100644 index a7fd70272eb..00000000000 --- a/.changeset/21922-metadata-protocol-meta-read-declines-code-datasource-row.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@objectstack/metadata-protocol": patch ---- - -The metadata door serves a code-defined datasource's code definition while a stored row under its name still exists - -Clause-②: no - -- `GET /api/v1/meta/datasource/:name`, the `GET /api/v1/meta/datasource` list and the `effective` layer of `GET /api/v1/meta/datasource/:name/layers` now skip a stored `sys_metadata` row under a datasource name the host registers from code: one an installed package declares in `*.datasource.ts`, or the host's `default`. They serve the in-memory code definition instead. The datasource admin door and the boot restore already serve that ("code wins on collision"). Before this change the stored row was served first, so the two doors answered with two different bodies for one name. -- The decision is made by name, through the same predicate the reads already ask for a shipped flow name. It never reads a row's `origin`. Every other type keeps ADR-0005's read order, in which the stored overlay wins. -- Unchanged: the row stays at rest and is still reported in the layered read's `overlay`. The read envelope stays `deletable: true` while the row exists, and `DELETE /api/v1/meta/datasource/:name` still removes it as the repair. A draft read (`state: 'draft'`, or the draft preview) is still answered from the draft row. A runtime datasource's stored row is served as before. -- The `/meta` and admin doors already refuse to write such a row. This change affects only how a row left from before that refusal is read. -- Not moved: `GET /api/v1/meta/datasource/:name/published` still serves the stored row, which is the active overlay row that route describes. -- ⛔ No public export, signature, schema or accept-set change. The built entry declarations gain two `private` member names on `ObjectStackProtocolImplementation`. diff --git a/.changeset/21923-datasource-origin-from-provenance.md b/.changeset/21923-datasource-origin-from-provenance.md deleted file mode 100644 index 955db84b499..00000000000 --- a/.changeset/21923-datasource-origin-from-provenance.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -"@objectstack/service-datasource": patch ---- - -The datasource admin door and the metadata door agree on a runtime datasource: a datasource saved through `/api/v1/meta/datasource/:name` is listed and editable through `/api/v1/datasources` in the same boot and after a restart, and one created through the admin door can be edited and removed through the metadata door (#21923) - -Clause-②: no - -`DatasourceSchema.origin` publishes `runtime` as "created via the Studio wizard, persisted in the runtime metadata store, environment-scoped, editable", and says `origin` is never accepted from client input. Three things broke that for a datasource the metadata door wrote, or one the admin door wrote: - -- **The admin door read `origin` from the record.** It served `origin ?? 'code'`, so after a restart a datasource saved through the metadata door (whose body carries no `origin`, or asserts `origin: 'code'`) was served as code-defined, and `PATCH /api/v1/datasources/:name` answered `400 DATASOURCE_ADMIN_ERROR` ("… is code-defined and cannot be edited at runtime."). It now serves `code` only for a name the host registers from code (the host's code-datasource set, the one the boot restore and the metadata door's refusal read), and `runtime` for every other name, whatever the record says. Boot pool rehydration follows the served origin, so such a datasource also gets its live pool after a restart. -- **A metadata-door write never reached the admin door until a restart.** The metadata door persisted the row but never registered it where the admin door lists, so in the same boot `GET /api/v1/datasources` omitted it and `PATCH` answered "not found". The datasource-admin plugin now registers the protocol's awaited `datasource` mutation projector: after a metadata-door save, publish, revert, rollback or delete, the admin door's registry follows the stored row and the live pool converges on it (opened on a create, rebuilt on a connectivity change, evicted on a delete) before the metadata door answers. A write under a name the host defines in code changes nothing here, so the metadata door's `DELETE` of a leftover row under such a name still leaves the code definition served. -- **An admin-created row could not be edited or removed through the metadata door.** The admin door stored its `sys_metadata` row with no `checksum`, the column the metadata door's optimistic lock compares, so `PUT` and `DELETE /api/v1/meta/datasource/:name` answered `409 METADATA_CONFLICT` for every admin-created datasource. The admin door now stamps the checksum the metadata door's repository stamps (`hashSpec` from `@objectstack/metadata-core`, which becomes a runtime dependency of this package). A row stored before this release has no checksum until the admin door next writes it: editing that datasource through the admin door once makes it editable through the metadata door. - -Cluster convergence (a peer replica's signal after an admin-door write) decides "code" from the same set, so a stored row under a code-defined name never opens a pool there, and every other stored row is pooled as runtime. - -**Unchanged.** A code-defined datasource stays read-only through both doors: the admin door's `PATCH` and `DELETE` still answer `400 DATASOURCE_ADMIN_ERROR`, and the metadata door's still answer `403 NOT_OVERRIDABLE`. A host that composes no code-datasource producer (neither `AppPlugin` nor `DefaultDatasourcePlugin`) registers no set, and the admin door then serves every datasource as runtime, as its boot restore already treats every stored row. diff --git a/.changeset/21967-view-expansion-per-package.md b/.changeset/21967-view-expansion-per-package.md deleted file mode 100644 index 7798183f66a..00000000000 --- a/.changeset/21967-view-expansion-per-package.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -'@objectstack/metadata-protocol': patch ---- - -fix(metadata-protocol): another package's withdrawal of a form holds at the anonymous form endpoints, whatever packages' copies of a view container are saved - -Clause-②: no - -- **What was wrong.** Where packages ship the same view container, the view list (`getMetaItems` for `view`) served one item for each name a saved environment-wide copy of that container expands: the copy's own expansion. Every other package's item of that name, shipped or saved, was left out. The anonymous form endpoints judge a withdrawal against the environment-wide view list, so they could miss another package's withdrawal of such a form. -- **What it does now.** The view list serves each package its own item of such a name: - - a package's saved copy of the container serves that package's item of each name it expands; - - a package-less saved copy stands in for every package that has no copy of its own (ADR-0048); - - any other package keeps its own item. - - So another package's withdrawal of a form holds at the anonymous form endpoints, whatever packages' copies of the container are saved. The organization-scoped save check reads the same list, so it judges each package's item too. -- **A stored view row of exactly such a name** keeps its own package's slot only. A package-less row still serves every package's slot. Before, any package's row of the name kept every package's copy expansion of it out of the list. -- **The by-name read agrees.** `getMetaItem` naming a package serves the item that package's slot in the list serves. Where no copy belongs to that package, a package-less copy now stands in for it. A list scoped to a package (`GET /api/v1/meta/view?package=`) serves the same item in each slot the package lists. A package-less copy adds no item to that list. -- **What does not change.** Within one package, a later expansion of a name still replaces an earlier one, and the save door's view container collision check is unchanged. A by-name read that names no package answers as before. No key, export, status or error code changes. diff --git a/.changeset/21968-spec-default-agent-chat-door.md b/.changeset/21968-spec-default-agent-chat-door.md deleted file mode 100644 index a835eda35c8..00000000000 --- a/.changeset/21968-spec-default-agent-chat-door.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -`App.defaultAgent` docblock: the agent route is the one chat door, and the console is what reads the key. - -Clause-②: no - -- The docblock no longer says the assistant chat endpoint (`POST /api/v1/ai/assistant/chat`) resolves this agent from `context.appName`. That route, with `GET /api/v1/ai/assistant` and `GET /api/v1/ai/assistant/skills`, was retired in the cloud AI runtime (objectstack-ai/cloud#2621, objectstack-ai/cloud#2651), and no server route reads `defaultAgent`. -- It now says who does read it. The console's chat dock hands the active app's `defaultAgent` to its one surface-to-agent resolver, which honours only `ask` or `build` (legacy aliases included) and otherwise falls back to the surface default. The resolved agent is then called by name on `POST /api/v1/ai/agents/:agentName/chat`, where the path segment, not this key, selects the agent. -- The ADR-0063 surface-binding paragraph and the rule that only the two platform agents resolve are unchanged, as is the note that the bare `POST /api/v1/ai/chat` resolves no agent. -- The docs page `ai/actions-as-tools` lists the agent route as the only in-product chat route. -- ⛔ No schema, parse, `.describe()`, export, type or accept-set change. The docblock ships in the published package, in the `dist/ui` and `dist/browser` JavaScript bundles and in the shipped `src/ui/app.zod.ts`, which is why this is a patch. diff --git a/.changeset/21972-caller-scoped-views-not-first.md b/.changeset/21972-caller-scoped-views-not-first.md deleted file mode 100644 index fc2859c9ab7..00000000000 --- a/.changeset/21972-caller-scoped-views-not-first.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/platform-objects": patch ---- - -Setup's identity pages open on the tenant-wide list, not on the administrator's own rows. Before this, Setup → API Keys, Sessions, OAuth Applications, Identity Links and User Preferences opened each object's first declared list view, which was the caller-scoped "My …" view (`user_id = {current_user_id}`), so an administrator saw only their own keys, sessions, applications, links and preferences. - -Clause-②: no - -- On `sys_api_key`, `sys_session`, `sys_oauth_application`, `sys_account`, `sys_user_preference` and `sys_user`, the unscoped "All" view (`all_keys`, `all_sessions`, `all_apps`, `all_links`, `all_preferences`, `all_users`) is now declared first, and the caller-scoped view (`mine`, `me`) second. A route that names no view, such as a record page's object breadcrumb or the object switcher, now opens the "All" view. No view is added, removed or changed. -- The Setup entries `nav_api_keys`, `nav_sessions`, `nav_oauth_apps`, `nav_accounts` and `nav_user_preferences` now name that view with `viewName`, as `nav_users` already did. The Account app's Linked Accounts entry (`nav_account_linked`) now names `mine`, like the other Account entries, so neither app depends on the declared order. -- The "My …" views are still tabs on each page. The declared order decides which view opens, not which rows a caller may read: row-level security still scopes a member's rows. -- The generated translation bundles follow the new view order. No translated text changed. -- ⛔ No schema, parse, export or accept-set change. diff --git a/.changeset/21972-record-shares-all-first.md b/.changeset/21972-record-shares-all-first.md deleted file mode 100644 index b1bef852c37..00000000000 --- a/.changeset/21972-record-shares-all-first.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@objectstack/plugin-sharing": patch ---- - -Setup → Record Shares opens on every share, not on the shares granted to the administrator. Before this, the entry named no view, and `sys_record_share` declared the caller-scoped "Granted to Me" view (`recipient_id = {current_user_id}`) first. - -Clause-②: no - -- `sys_record_share` now declares its unscoped "All" view (`all_shares`) first. "Granted to Me" and "Granted by Me" follow it, still as tabs. No view is added, removed or changed. -- The Setup entry `nav_record_shares` now names `all_shares` with `viewName`, so it does not depend on the declared order. -- The generated translation bundles follow the new view order. No translated text changed. -- ⛔ No schema, parse, export or accept-set change. diff --git a/.changeset/21973-nav-view-name-default.md b/.changeset/21973-nav-view-name-default.md deleted file mode 100644 index 6e80d205830..00000000000 --- a/.changeset/21973-nav-view-name-default.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -`ObjectNavItemSchema.viewName`'s describe no longer says the default is "all". It now states what the console does when an object nav entry names no view: it opens the object's default list view, else its first declared list view. `all` is only the console's fallback tab, and it exists only for an object that declares no list view. - -Clause-②: no - -- The rule is read from objectui at the `.objectui-sha` pin. `ObjectView` opens `defaultViewId || views[0]`, where `defaultViewId` is the view `buildViewTabs` marks `isDefault` (the default `list`). `buildViewTabs` adds the `all` tab only when the object has no list view at all. -- An author who omitted `viewName` expecting all records got that default or first declared view instead. When the object declares more than one list view, name the one the entry should open in `viewName`. -- The generated app reference page follows (#21973). The lint header that quoted the old sentence follows too, as a comment only. -- ⛔ No schema, type, optionality, default, export or accept-set change. The console's behaviour does not change. diff --git a/.changeset/21978-checksum-less-row-served-version.md b/.changeset/21978-checksum-less-row-served-version.md deleted file mode 100644 index c6d515e4951..00000000000 --- a/.changeset/21978-checksum-less-row-served-version.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@objectstack/metadata-protocol': patch ---- - -A metadata row stored with no `checksum` can be edited and removed through the metadata door (#21978) - -Clause-②: no - -- `SysMetadataRepository` serves a `sys_metadata` row that carries no `checksum` as the hash of its stored body, but its `put` and `delete` compared the caller's parent with the raw column (`null`). So `PUT` and `DELETE /api/v1/meta/:type/:name` answered `409 METADATA_CONFLICT` ("Expected parent … but current is null") for every such row, with `If-Match` set to the version the door served and with no `If-Match` (last-write-wins) alike. A publish over such a row was refused the same way, as were the rollback and commit-revert doors, which take their parent from the same read. The datasource admin door stored such rows before it stamped them. -- `put` and `delete` now accept the version such a row is served as. A `null` parent still matches it, and a row with a `checksum` is judged exactly as before. A stale version is still refused with `409 METADATA_CONFLICT`, and the refusal now names the row's served version as the current one instead of `null`. -- The next write stamps the row's `checksum`, as every write does. Stored rows are not rewritten. -- Publishing a draft row stored with no `checksum` now also removes that draft row. Before, the post-promotion cleanup was refused by the same lock and the draft stayed pending, with nothing reported. diff --git a/.changeset/21980-copy-overlays-shipped-names.md b/.changeset/21980-copy-overlays-shipped-names.md deleted file mode 100644 index f3ef8468e20..00000000000 --- a/.changeset/21980-copy-overlays-shipped-names.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@objectstack/metadata-protocol': minor ---- - -A package's stored copy of a view container it ships overlays that package's shipped views, so a withdrawal saved in the copy holds at the anonymous form endpoints; the runtime save door refuses three copies it accepted before - -Clause-②: no (narrowing) - - - -**BREAKING** accept-set narrowing at the runtime save door, shipped as `minor` under the repo's launch-window convention for breaking changes, the grade the same door's earlier view container refusals shipped with. - -- **What was wrong.** The source loaders register a view container a package ships as `OBJECT.KEY` views for that package, whichever package owns the object. When that package stored a copy of the same container (a `PUT /api/v1/meta/view/NAME` of the container) and the object belonged to another code package, the copy expanded under its own name instead, as `OBJECT.CONTAINER.KEY` (a bare `list` as `OBJECT.CONTAINER`). So the copy overlaid none of the views its package ships: a form withdrawn from anonymous intake in the copy stayed open in the package's shipped form of that name, and the anonymous form endpoints kept serving it. -- **What it does now.** A package's stored copy of a container that package ships, bound to the same object, expands as the loaders expand the shipped container: each member is served under the loaders' name, `OBJECT.KEY`, in the copying package's own slot on the view list and on the by-name read naming that package. So a withdrawal saved in the copy holds at the anonymous form endpoints. The copy still declares no default view for an object another package owns. Any other container on another package's object keeps expanding under its own name, unchanged. -- **The by-name read on an unscoped kernel.** On a kernel with no environment id, a stored container's expanded views are also registered in the schema registry, under the bare name. A view whose name another package also ships is no longer registered there: the registry answered that bare entry ahead of the other package's own view, so `getMetaItem` naming the other package served this container's view. Every kernel's by-name read already serves such a view from its stored row, for its own package and for a read that names no package. Two packages that ship one container, with one of them storing a copy, were affected before this change too. - -**What is refused now.** `saveMetaItem`, which `PUT /api/v1/meta/view/NAME` and the dispatcher's metadata save both call, judges a copy at the names it now expands to, so three copies it accepted before are refused with `VALIDATION_ERROR` / 400, before anything is stored. Each is a package's copy of a container it ships on another package's object: - -- The copy adds a bare `list` whose name, `OBJECT.default`, only the other package ships. Before: accepted, served as `OBJECT.CONTAINER`. After: refused, naming the package that ships `OBJECT.default`. -- The copy adds a keyed member (a `formViews` or `listViews` entry, a named `list`, or a `form`) whose name, `OBJECT.KEY`, only the other package ships. Before: accepted, served as `OBJECT.CONTAINER.KEY`. After: refused, naming the package that ships `OBJECT.KEY`. -- Another stored container, saved under a different name, already expands a name the copy now expands. Before: accepted. After: refused, naming that stored container. - -**The fix.** For the first two, give the added member a key of its own that no package ships and no stored container expands, or save a view item (`name`, `object`, `viewKind`, `config`) under that name to override the other package's view. For the third, add the view as a member of the stored container that already expands the name, or save a view item under that name. - -**What still saves.** A copy that keeps the members its package's shipped container has, their contents edited, under the container's own name. A copy that adds a member under a key no package ships and no stored container expands. A view item under any of these names. Every container that is not a copy of its own package's shipped container, as before. - -**Rows stored before this change.** They keep their bytes. A stored copy of a container its package ships, on another package's object, is now served under the loaders' names (`OBJECT.KEY`) instead of `OBJECT.CONTAINER.KEY`, so it overlays the package's shipped views from the next read on, with no re-save. A reference to one of its old names (a navigation `viewName`, a form action `target`) no longer resolves; point it at `OBJECT.KEY`. A new save of a stored copy in one of the refused shapes, a re-save included, is refused until its body stops colliding. Delete stays open. diff --git a/.changeset/21984-approval-requests-all-first.md b/.changeset/21984-approval-requests-all-first.md deleted file mode 100644 index 59a12113fc3..00000000000 --- a/.changeset/21984-approval-requests-all-first.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/plugin-approvals": patch ---- - -Setup → Approvals → Requests opens on every approval request, not on the requests pending on the administrator. Before this, the entry named no view, and `sys_approval_request` declared the caller-scoped "My Pending" view (`pending_approvers contains {current_user_id}`) first, so the console opened it. - -Clause-②: no - -- `sys_approval_request` now declares its unscoped "All" view (`all_requests`) first. "My Pending", "I Submitted" and "Completed" follow it in their previous order, still as tabs. A route that names no view, such as a record page's object breadcrumb or the object switcher, now opens "All". No view is added, removed or changed. -- The Setup entry `nav_approval_requests` now names `all_requests` with `viewName`, so it does not depend on the declared order. The Account app's Approvals entry opens the Approvals Inbox component and reads neither. -- The declared order decides which view opens, not which rows a caller may read. -- The generated translation bundles follow the new view order. No translated text changed. -- ⛔ No schema, parse, export or accept-set change. diff --git a/.changeset/21986-metadata-protocol-declines-stored-row-public.md b/.changeset/21986-metadata-protocol-declines-stored-row-public.md deleted file mode 100644 index 79fad4e69de..00000000000 --- a/.changeset/21986-metadata-protocol-declines-stored-row-public.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@objectstack/metadata-protocol": minor ---- - -`ObjectStackProtocolImplementation.declinesStoredRow(type, name)` is now public, so a door that serves a stored row out of the layered read can ask the same decision the reads make - -Clause-②: yes (widening) - -- The method answers `true` for exactly the names whose stored `sys_metadata` row the active reads (`getMetaItem`, the list, and the `effective` layer of `getMetaItemLayered`) do not adopt: a flow name a managed package ships (the answer `isShippedFlowName` gives), and a datasource name the host registers from code (one an installed package declares in `*.datasource.ts`, or the host's `default`). Every other type and name answers `false`. -- The `GET /meta/:type/:name/published` doors in `@objectstack/rest` and `@objectstack/runtime` now ask this method in place of `isShippedFlowName`. A door asks it, and does not restate either half or the host's code-datasource set. -- `isShippedFlowName` stays public and unchanged. -- The only change to the public surface is this one added method. No signature, schema or accept set changes, and no behaviour of this package changes. diff --git a/.changeset/21986-rest-published-door-code-datasource.md b/.changeset/21986-rest-published-door-code-datasource.md deleted file mode 100644 index 95f62eea4f8..00000000000 --- a/.changeset/21986-rest-published-door-code-datasource.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@objectstack/rest": patch ---- - -`GET /api/v1/meta/datasource/:name/published` serves a code-defined datasource's code definition while a stored row under its name still exists - -Clause-②: no - -- For a datasource name the host registers from code (one an installed package declares in `*.datasource.ts`, or the host's `default`), the published door now serves the layered read's `effective` layer, which is the code definition. Before this change it served the leftover stored `sys_metadata` row, with that row's label, `origin` and connection settings, while `GET /api/v1/meta/datasource/:name`, the `/meta/datasource` list and `/layers` all served the code definition. The door now asks the protocol's `declinesStoredRow`, the one decision those reads make, in place of `isShippedFlowName`. A shipped flow name is answered as before. -- Unchanged: a runtime datasource's stored row is still what the door serves, and so is every stored row of every other type. A protocol that does not provide `declinesStoredRow` still gets the stored row. The row itself stays at rest, `/layers` still reports it in `overlay`, and `DELETE /api/v1/meta/datasource/:name` still removes it as the repair. diff --git a/.changeset/21986-runtime-published-door-code-datasource.md b/.changeset/21986-runtime-published-door-code-datasource.md deleted file mode 100644 index d721fe19249..00000000000 --- a/.changeset/21986-runtime-published-door-code-datasource.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -"@objectstack/runtime": patch ---- - -The runtime dispatcher's `GET /meta/datasource/:name/published` serves a code-defined datasource's code definition while a stored row under its name still exists - -Clause-②: no - -- This is the dispatcher twin of the `@objectstack/rest` published door, and it now answers the same way. For a datasource name the host registers from code (one an installed package declares in `*.datasource.ts`, or the host's `default`), the door serves the layered read's `effective` layer, which is the code definition, instead of the leftover stored row. It asks the protocol's `declinesStoredRow` in place of `isShippedFlowName`. A shipped flow name is answered as before. -- Unchanged: a runtime datasource's stored row, and every stored row of every other type, is served as before. So is every row when the protocol does not provide `declinesStoredRow`. diff --git a/.changeset/21995-measure-column-aggregate.md b/.changeset/21995-measure-column-aggregate.md deleted file mode 100644 index 06b997295f9..00000000000 --- a/.changeset/21995-measure-column-aggregate.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/service-analytics": minor ---- - -feat(spec,analytics): a dataset answer's measure column states its aggregate, labelled or not (`fields[].aggregate`) - -Clause-②: yes (widening) - -- **What a renderer can now read.** Each measure column of a dataset answer (`POST /analytics/dataset/query`) carries `fields[].aggregate`: the aggregate its dataset measure declares, in the closed `AggregationFunction` vocabulary (`count`, `sum`, `avg`, `min`, `max`, `count_distinct`). It is there whether or not the author gave the measure a `label`. So a chart can tell a count from a sum, for example to draw whole-number axis ticks for a count instead of 0.75 / 1.5 / 2.25. -- **What was missing.** The only aggregate on the wire was `builtinAggregate`, and it is present only when the measure has no `label`. A labelled measure, such as a `count` named "Tasks", reached the wire as `{ name, type: 'number', label }`, with nothing to say what kind of number it was. -- **Where it is set.** `AnalyticsService` writes it in the one step that describes a dataset answer's columns from the dataset's own measures. That step runs for both the live query and the draft-data preview, so the two answers agree. A measure's `__compare` column carries the same aggregate. -- **Where it is absent.** Dimension columns. Derived measures, which combine other measures and have no single aggregate (a stray `aggregate` written beside `derived` is ignored when the dataset compiles, so it is not stated here either). And a cube query answer (`POST /analytics/query`), which does not run through the dataset column step. -- **Unchanged.** `builtinAggregate` keeps its meaning: present only on a label-less measure column, to mark a header that is the server's default. No authoring key is added; `aggregate` is a response member only. `AnalyticsResultResponseSchema` and the `AnalyticsResult` contract declare the member, and the REST route relays it as it does every other column key. diff --git a/.changeset/22012-isplatformadmin-standing.md b/.changeset/22012-isplatformadmin-standing.md deleted file mode 100644 index 5741e597ee9..00000000000 --- a/.changeset/22012-isplatformadmin-standing.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -`EvalUserSchema.isPlatformAdmin` is no longer marked deprecated. Its describe and docblock now say what the key reports: the `PLATFORM_ADMIN` standing of ADR-0095 D3. - -Clause-②: no - -- The platform resolves that standing per request, from the deployment's declared administrator list (`OS_PLATFORM_OWNER_EMAIL`) under every tenancy posture, or from an unscoped `admin_full_access` grant under the `single` posture. -- It is the predicate platform-operator gates read: `current_user.isPlatformAdmin == true` (ADR-0068 D4). The session payload emits it from the posture rung, and the platform-admin route gate reads it. -- The resolver projects the `platform_admin` name into `positions` from the same grant, so the name and the key agree for every genuine administrator. Gate on the key, never on `'platform_admin' in current_user.positions`. -- The old text, "DERIVED alias of 'platform_admin' in positions. Deprecated.", described a reading ADR-0095 D3 superseded. ADR-0068 carries dated notes under D2 and D4 that say so. -- ⛔ Nothing you author changes. There is no schema, type, optionality, default, export or accept-set change, and `createEvalUser` computes exactly what it did. A predicate that already reads `current_user.isPlatformAdmin` keeps working and is the supported form. diff --git a/.changeset/22019-object-save-door-formula-verdict.md b/.changeset/22019-object-save-door-formula-verdict.md deleted file mode 100644 index c7f96446b97..00000000000 --- a/.changeset/22019-object-save-door-formula-verdict.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"@objectstack/lint": minor -"@objectstack/metadata-protocol": minor ---- - -fix(lint)!: the object save door refuses a formula field whose expression `os build` refuses (#22019) - -Clause-②: no (narrowing) - -`formulas.mdx` says the same `validateExpression` validator backs `os build` and metadata registration. At the object save door it did not. A formula field calling an unregistered function, such as `sqrt(record.amount)`, was refused by `os build` as an unknown function, but `PUT /api/v1/meta/object/:name` answered 200, stored it, and the field read `null` on every row. - -The runtime publish gate now runs the build's own formula check on an object write. The registry entry for the build's expression rule (`validateStackExpressions`) declared the flow, action and hook writes and never the object write, so the gate never dispatched it there. It now declares `object` as well, for one of its passes: a formula field's `expression`. The door's verdict is the build's finding: the same rule id (`expression-invalid`), location (`object 'NAME' · field 'FIELD' expression`), message and hint. - -**BREAKING — what moves for consumers.** - -- An object write in publish mode answered 200 for a formula field whose expression the shared validator refuses. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that field's `expression`. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`). -- The verdict is the one `os build`, `os validate` and `os lint` already gave: an unknown function, a field the object does not declare, a bare field reference (`amount` instead of `record.amount`), and the other errors in the build's formula check. Its warnings now ride the save response as advisories, as they already did for a flow write. - -**Remedy.** Fix the expression: the message names the unknown function or field and the position, as `os build` already requires. Use one of the functions `introspectScope` lists, qualify field reads as `record.FIELD`, or compute the value in a stored field and reference it. Saving it as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged. - -**Unchanged.** - -- Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps reading, with the formula still `null`, until it is next saved. At that save the gate judges it, because the differential compares the write against the stored universe without its own stored row. -- The other expressions an object carries are still not judged at this door: validation-rule predicates, the field-rule slots (`requiredWhen`, `readonlyWhen`, `conditionalRequired`, `visibleWhen`), option `visibleWhen`, and the object's own action predicates. `os build` judges them, and the door does not, as before. Each needs its own crossing, measured over the stored corpus first. -- `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write. -- Measured before crossing: every formula field this repository ships has 0 refusals and 0 advisories at the door. That is 29 fields on 28 objects: examples 7 on 6, and the platform `display_title` formulas 22 on 22. -- No public export or signature moves. `validateStackExpressions(stack)` keeps its signature. The registry entry reaches the passes through an internal function that is not on the package's entry. The built entry declarations differ only in one doc comment, on `AuthoringRuleContext.runtimeWriteType`. - - diff --git a/.changeset/22019-objectql-formula-fault-log.md b/.changeset/22019-objectql-formula-fault-log.md deleted file mode 100644 index 0dc203b573c..00000000000 --- a/.changeset/22019-objectql-formula-fault-log.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@objectstack/objectql": patch ---- - -fix(objectql): a formula field that does not evaluate is logged once per object and field, instead of reading `null` in silence (#22019) - -A formula the engine cannot evaluate reads `null`, on `find`, on `findOne` and on the write response. Before this change nothing said why. A formula calling an unregistered function (`sqrt(record.amount)`) read `null` on every row with no log line anywhere. ADR-0032 says a call site must not silently swallow an expression fault. - -The engine now reports the fault through its logger at `warn`, once per (object, field) per engine instance, however many rows and reads hit it. The line names the object, the field and the evaluator's error (kind and first line; the full message is in the log metadata). It also says where the repair is: `os validate` or a re-save of the object refuses an expression-level fault with a located message, and a fault that depends on a record's values needs a guard on the operands it reads. - -Unchanged: the field still reads `null`, because what a read returns is protocol. `evaluateFormulaField`, the hook-side helper with no engine, still returns `null` without a log line. The built entry declarations gain three `private` member names on `ObjectQL`. diff --git a/.changeset/22024-no-package-read-wears-served-package.md b/.changeset/22024-no-package-read-wears-served-package.md deleted file mode 100644 index 6746c586e44..00000000000 --- a/.changeset/22024-no-package-read-wears-served-package.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@objectstack/metadata-protocol': patch ---- - -A by-name metadata read that names no package now wears the package of the body it serves - -Clause-②: no - -- **What was wrong.** `getMetaItem` with no `packageId` (behind `GET /api/v1/meta/TYPE/NAME` when no package is named) merges the registry artifact's protection envelope, `_packageId`, `_packageVersion` and `_provenance`, over the body it serves. With no package named, that envelope was the first-registered package's. When two installed packages ship one name and the body served was the other package's, the answer carried that body under the wrong package. Two cases were measured. In the first, both packages ship a view container and one of them stores a copy of it: the read served the copy's view and named the first-registered package. In the second, a stored row of the name is bound to one package. The answer's top-level `packageId` / `provenance` / `packageVersion` fields, which are read off the served item, said the same wrong thing. -- **What it does now.** With no package named, the envelope is looked up at the package the served item is bound to. That is the stored row's package, the package of the container copy that expands the name, or the `_packageId` of the MetadataService or registry item. This is the rule the `GET /api/v1/meta/TYPE` list already applies to each item it serves, so the list and the by-name read now give one envelope for one served body. -- **Unchanged.** Which body the read serves. A read naming a package. The lock family and the `lock` / `editable` / `deletable` envelope, which still come from the item-lock resolution over the read's own address. A served item bound to no package (a package-less stored row or copy, or a registry entry with no package) keeps the package-less lookup it had, so for a name only one package ships, a tenant's package-less overlay still wears that package's envelope. The layered read (`/layers`) is not changed. -- ⛔ No public export, signature, schema or accept-set change. Nothing is accepted or refused differently. diff --git a/.changeset/22026-read-receipt-recipient-only.md b/.changeset/22026-read-receipt-recipient-only.md deleted file mode 100644 index 173f412407f..00000000000 --- a/.changeset/22026-read-receipt-recipient-only.md +++ /dev/null @@ -1,17 +0,0 @@ ---- -'@objectstack/service-messaging': minor ---- - -fix(service-messaging)!: mark-read writes a read receipt only for a notification delivered to that user - -Clause-②: no (narrowing) - - - -**BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention: `MessagingService.markRead` — the method behind the notifications mark-read door, and behind `markReadAsCaller` — writes a `read` receipt only for a notification that was delivered to that user. A read receipt belongs to a recipient (ADR-0030 keys it by recipient). - -- **Delivered** means a receipt keyed on that user already exists (it is flipped to `read` in place, as before), or the user's inbox holds a message for that notification. The inbox message is enough on its own, because the inbox channel's `delivered` receipt is best-effort. -- **Any other id** writes no receipt, is not counted in `readCount`, and its notification's organization is not read. The response shape `{ success, readCount }` is unchanged. -- **`markAllRead`** is unchanged: every id it sweeps comes from the user's own inbox. - -What changes for you: nothing in what you write. A `readCount` lower than the number of ids sent means some of them were not notifications delivered to that user, and nothing was written for those. diff --git a/.changeset/22028-control-plane-url-docblock.md b/.changeset/22028-control-plane-url-docblock.md deleted file mode 100644 index 7e62b103dc5..00000000000 --- a/.changeset/22028-control-plane-url-docblock.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -"@objectstack/cloud-connection": patch ---- - -`RuntimeConfigPluginConfig.controlPlaneUrl`'s published docblock no longer says that an empty string declares "this runtime IS the cloud". It now says what the constructor does: `''` keeps marketplace and install requests on this origin, and that is all it says. - -Clause-②: no - -- The runtime that passes `''` may serve the catalog itself or proxy a control plane it does not name. The CLI's cloud-connected `os serve` passes `''` while its marketplace proxy forwards to the control plane `resolveCloudUrl()` answers. So `''` reads neither as "this runtime is the cloud" nor as "there is no upstream". This matches the `AppShellRuntimeConfig.cloudUrl` doc in `@object-ui/app-shell`. -- A runtime with no control plane says so with a decline spelling (`'off'` / `'none'` / `'local'` / `'disabled'`), in `controlPlaneUrl` or in `OS_CLOUD_URL`. The docblock now says this too. -- ⛔ No code, type, export or default change. The served `cloudUrl` and the telemetry posture do not change. diff --git a/.changeset/22032-object-save-door-field-rule-slots.md b/.changeset/22032-object-save-door-field-rule-slots.md deleted file mode 100644 index b15fe741224..00000000000 --- a/.changeset/22032-object-save-door-field-rule-slots.md +++ /dev/null @@ -1,31 +0,0 @@ ---- -"@objectstack/lint": minor -"@objectstack/metadata-protocol": minor ---- - -fix(lint)!: the object save door refuses a field-rule slot whose predicate `os build` refuses (#22032) - -Clause-②: no (narrowing) - -`formulas.mdx` says the same `validateExpression` validator backs `os build` and metadata registration. For a field's rule slots it did not, at the object save door. A field whose `requiredWhen` read a bare field, such as `amount > 1`, or whose `visibleWhen` called an unregistered function, such as `sqrt(record.amount) > 1`, was refused by `os build` at error, but `PUT /api/v1/meta/object/:name` answered 200 and stored it. - -The runtime publish gate now runs the build's field-rule-slot check on an object write. The build's expression rule (`validateStackExpressions`) was already on the object door for formula fields and validation-rule predicates. On an object write it now also judges each field's `requiredWhen`, `readonlyWhen` and `visibleWhen` the way the build does, with the build's three gates on them: the `parent` gate, the null-guard check over `requiredWhen`, and the refusal of a `requiredWhen` or `readonlyWhen` that reads through a reference field. The door's verdict is the build's finding: the same rule id (`expression-invalid`), location (`object 'NAME' · field 'FIELD' SLOT`), message and hint. - -**BREAKING — what moves for consumers.** - -- An object write in publish mode answered 200 for a field whose `requiredWhen`, `readonlyWhen` or `visibleWhen` the shared validator refuses. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that slot. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`). -- The verdict is the one `os build`, `os validate` and `os lint` already gave: an unknown function, a field the object does not declare, a bare field reference (`amount` instead of `record.amount`), a syntax error, a root a field-level rule never binds (such as `current_user`), a `parent` read on an object that does not declare exactly one `master_detail` relationship, an ordering or arithmetic operator in `requiredWhen` applied to a nullable field with no `!= null` guard, and a `requiredWhen` or `readonlyWhen` that reads through a reference field (`record.account.tier`, or `parent.REF.FIELD`). Its warnings now ride the save response as advisories. -- A detail object's `requiredWhen` or `readonlyWhen` that reads through one of its master's reference fields (`parent.REF.FIELD`) is judged whenever the master is in the write's context, and that includes a save of the master itself. So a master save can answer 422 with an issue located at a stored detail's field. Fix the detail's predicate, then save the master again. - -**Remedy.** Fix the predicate: the message names the unknown function or field, the unbound root, the unguarded operand or the reference read, and the position, as `os build` already requires. Qualify field reads as `record.FIELD`, use one of the functions `introspectScope` lists, guard a nullable operand in `requiredWhen` with `record.FIELD != null && …`, and move a check that must read through `record.REF` into a `validations[]` `script` rule, whose `condition` is read one hop through a reference; a read through `parent.REF` has no such surface, so read a column the master declares instead (denormalise the value onto it). Saving it as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged. - -**Unchanged.** - -- Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps loading until it is next saved. At that save the gate judges it, because the differential compares the write against the stored universe without its own stored row. -- `conditionalRequired` is still refused at the save door's schema step, before this gate, as a key retired in protocol 17; `os build` judges it as a field-rule slot as before. -- Option `visibleWhen` and the object's own action predicates are still not judged at this door. `os build` judges them, and the door does not, as before. -- `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write. -- Measured before crossing: every field-rule slot this repository ships has 0 refusals and 0 advisories, at the build and at the door. That is 9 slots on 8 fields of 3 objects (examples: 8 on `showcase_invoice` and `showcase_invoice_line`, three of them `parent`-scoped; the platform: 1 on `sys_permission_set`), over the 118 objects this repository ships. -- No public export or signature moves. `validateStackExpressions(stack)` keeps its signature, and no registry entry changes: the expression rule already declared `object`. - - diff --git a/.changeset/22032-object-save-door-validation-predicates.md b/.changeset/22032-object-save-door-validation-predicates.md deleted file mode 100644 index b6630ed55bf..00000000000 --- a/.changeset/22032-object-save-door-validation-predicates.md +++ /dev/null @@ -1,29 +0,0 @@ ---- -"@objectstack/lint": minor -"@objectstack/metadata-protocol": minor ---- - -fix(lint)!: the object save door refuses a validation rule whose predicate `os build` refuses (#22032) - -Clause-②: no (narrowing) - -`formulas.mdx` says the same `validateExpression` validator backs `os build` and metadata registration. For a validation rule's predicates it did not, at the object save door. A rule whose `condition` called an unregistered function, such as `sqrt(record.amount) > 1`, or read a bare field, such as `amount > 1`, was refused by `os build` at error, but `PUT /api/v1/meta/object/:name` answered 200 and stored it. The rule then faulted on every write it judged. - -The runtime publish gate now runs the build's validation-rule check on an object write. The build's expression rule (`validateStackExpressions`) was already on the object door for formula fields alone. On an object write it now also runs its validation-rule pass: each `validations[]` rule's `condition` and a `conditional` rule's `when`, plus the null-guard check over every predicate the rule carries, its nested `then` and `otherwise` rules included. The door's verdict is the build's finding: the same rule id (`expression-invalid`), location (`object 'NAME' · validation 'RULE'`, or `… validation rule 'RULE' then → 'CHILD'` for a nested predicate), message and hint. - -**BREAKING — what moves for consumers.** - -- An object write in publish mode answered 200 for a validation rule whose predicate the shared validator refuses. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that rule. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`). -- The verdict is the one `os build`, `os validate` and `os lint` already gave: an unknown function, a field the object does not declare, a bare field reference (`amount` instead of `record.amount`), a syntax error, an ordering or arithmetic operator applied to a nullable field with no `!= null` guard (`has()` is no guard here), and the other errors in the build's validation-rule check. Its warnings now ride the save response as advisories. - -**Remedy.** Fix the predicate: the message names the unknown function or field, or the unguarded operand, and the position, as `os build` already requires. Qualify field reads as `record.FIELD`, use one of the functions `introspectScope` lists, and guard a nullable operand with `record.FIELD != null && …`. Saving it as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged. - -**Unchanged.** - -- Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps loading until it is next saved. At that save the gate judges it, because the differential compares the write against the stored universe without its own stored row. -- The other expressions an object carries are still not judged at this door: the field-rule slots (`requiredWhen`, `readonlyWhen`, `conditionalRequired`, `visibleWhen`), option `visibleWhen`, and the object's own action predicates. `os build` judges them, and the door does not, as before. -- `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write. -- Measured before crossing: every validation rule this repository ships has 0 refusals and 0 advisories, at the build and at the door. That is 21 rules carrying 13 predicates on 10 objects: examples 11 predicates on 7 objects, and the platform objects 2 on 3 (one rule on `sys_user` carries no predicate). -- No public export or signature moves. `validateStackExpressions(stack)` keeps its signature, and no registry entry changes: the expression rule already declared `object`. - - diff --git a/.changeset/22044-shared-entry-drops-conversion-table.md b/.changeset/22044-shared-entry-drops-conversion-table.md deleted file mode 100644 index ad8e1965bd4..00000000000 --- a/.changeset/22044-shared-entry-drops-conversion-table.md +++ /dev/null @@ -1,11 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -A browser bundle that imports from `@objectstack/spec/shared` or the package root no longer keeps the ADR-0087 conversion table unless it uses it - -Clause-②: no - -Each published entry is one flat file, so a consumer's bundler keeps every top-level call it cannot prove pure, together with everything that call references. Two such calls built the conversion table when the module loaded: the major-18 list's `inApplicationOrder(...)` and the flattening into `ALL_CONVERSIONS`. So every bundle of an entry that reaches the table kept all of it: every conversion, plus the view, field, page-component, dashboard, chart and report schemas the conversions read. `./shared` reaches the table only through `normalizeStackInput`, so a bundle that imported an expression schema from it carried the table too, and 17.7.0's new conversions made that copy larger. Both calls now carry a `@__PURE__` annotation, so a bundle keeps the table only when something it keeps reads it, for example `defineStack`, `normalizeStackInput` or `applyConversions`. - -Measured on objectui's console (objectui `c0862c1c`), against the same build with this package's previous source: the first screen's eager closure is 226,238 bytes gzip smaller, all of it in the `vendor-objectstack` chunk. Every entry's export list, every declaration and every runtime value is unchanged. Only the bytes a bundler keeps change. diff --git a/.changeset/22047-spec-ui-anonymous-form-intake.md b/.changeset/22047-spec-ui-anonymous-form-intake.md deleted file mode 100644 index 251ce8161f0..00000000000 --- a/.changeset/22047-spec-ui-anonymous-form-intake.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@objectstack/spec': minor -'@objectstack/metadata-core': patch ---- - -`@objectstack/spec/ui` now exports the rule that decides which forms a `view` body opens to anonymous intake, so a console reads "published" from the same rule the server's anonymous form doors serve - -Clause-②: yes (widening) - -- **New on `@objectstack/spec/ui`:** `publicFormSlug`, `anonymousFormIntakeSlug`, `anonymousFormIntakeCandidates`, `anonymousFormIntakeSlugs` and the `AnonymousFormIntakeCandidate` type. They lived only in `@objectstack/metadata-core`, which a browser console should not depend on. They are pure functions with no imports, beside the `SharingConfigSchema` they read. -- **What they decide is unchanged.** A form is open when its `sharing` has `enabled === true`, `allowAnonymous === true` and a non-empty `publicLink`. The scan covers the same three shapes in the same order: the nested `form`, every `formViews` entry, then the `config` of a `viewKind: 'form'` item. -- **`@objectstack/metadata-core` re-exports the same functions** from `@objectstack/spec/ui`. They are the spec's own bindings, not wrappers or copies, so there is still one copy of the rule. Its exports, names and types are unchanged, and `@objectstack/rest` and `@objectstack/metadata-protocol` keep importing from it. Its built output now loads `@objectstack/spec/ui` to get them. -- **Not covered by the new export:** whether another metadata layer withdraws a form (`anonymousFormIntakeWithdrawnIn`), and whether the deployment's tenancy posture lets the form take an anonymous submission (`anonymousFormIntakeUnavailability`). These two read server state and stay in `@objectstack/metadata-core`. `anonymousFormObjectName`, which names the object a form submits into, stays there beside them; it is a pure read of the form and the view, not of server state. A form the new functions call open can still be withheld by a withdrawal in another layer or by the posture. diff --git a/.changeset/22049-share-password-header-encoding.md b/.changeset/22049-share-password-header-encoding.md deleted file mode 100644 index bd58704fb11..00000000000 --- a/.changeset/22049-share-password-header-encoding.md +++ /dev/null @@ -1,18 +0,0 @@ ---- -"@objectstack/types": minor -"@objectstack/plugin-sharing": minor -"@objectstack/runtime": minor -"@objectstack/plugin-hono-server": minor -"@objectstack/hono": minor ---- - -feat(sharing): a share-link password can be sent in the `X-Share-Password` header whatever its characters, under a declared encoding (`X-Share-Password-Encoding: utf-8`) - -Clause-②: yes (widening) - -- **What was missing.** A browser cannot put a character above U+00FF in a request header (`Headers` throws a `TypeError` before the request leaves), and it strips leading and trailing spaces. `createLink` accepts any password, so a link whose password has a CJK character or an emoji could not be opened through the header. -- **What is now accepted.** A new companion request header, `X-Share-Password-Encoding`, declares how `X-Share-Password` is encoded. Its one value is `utf-8`, compared case-insensitively. Under it, `X-Share-Password` carries the password's UTF-8 bytes percent-encoded, as `encodeURIComponent(password)` produces them, and both public routes (`GET /api/v1/share-links/:token/resolve` and `/:token/messages`) decode it on both mounts: the sharing plugin's routes and the runtime dispatcher's `/share-links` domain. Both read the pair through one helper, `readSharePasswordHeader`, exported from `@objectstack/types` with the header-name constants. -- **Unchanged.** Without `X-Share-Password-Encoding`, `X-Share-Password` is read raw, exactly as before, so every value a client sends today resolves as it did. That includes a Latin-1 password and a raw password containing `%`; the server never percent-decodes a value nobody declared encoded. The `?password=` query parameter is still read first, and when it is present the header pair is not read. -- **What is refused.** `X-Share-Password-Encoding` naming any other value, or a password header that is not percent-encoded UTF-8 under `utf-8` (a `%` without two hex digits, octets that are not well-formed UTF-8, a character outside visible ASCII), answers `400 VALIDATION_FAILED` before the token is looked up. It is never compared raw instead. The message names the headers and the rule, never the presented value. -- **Response headers.** Both public routes now answer `Vary: X-Share-Password, X-Share-Password-Encoding`, still beside `Cache-Control: no-store`. -- **Cross-origin clients.** `X-Share-Password-Encoding` is in the default CORS preflight allow-list (`DEFAULT_CORS_ALLOW_HEADERS` in `@objectstack/plugin-hono-server`, which the `@objectstack/hono` adapter also applies). A deployment that passes its own `allowHeaders` must add `X-Share-Password-Encoding` beside `X-Share-Password` to let a cross-origin client send an encoded password. diff --git a/.changeset/22054-notify-title-template-input.md b/.changeset/22054-notify-title-template-input.md deleted file mode 100644 index fa6bac29c69..00000000000 --- a/.changeset/22054-notify-title-template-input.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/service-automation": patch ---- - -feat(spec)!: `NotifyConfigSchema.title` and `NotifyConfigSchema.message` are template slots: each takes a bare string or a `{ dialect: 'template', source }` envelope (the `tmpl` helper), as the expression dialect table already listed notification subjects and bodies among the `template` slots, and a blank bare string is now refused there - -Clause-②: yes (narrowing) - - - -**BREAKING** accept-set narrowing at two authorable keys (`automation/NotifyConfig:title`, `automation/NotifyConfig:message`), shipped as `minor` under the repo's launch-window convention for breaking changes. It is the grade `TemplateExpressionInputSchema`'s blank-string rule shipped with when it reached the first twelve typed keys. - -- **What widens.** Both keys are typed with `TemplateExpressionInputSchema`, the input every other `template` slot uses. Before, both were `z.string()`, so a notify node written with `` tmpl`…` `` passed `defineFlow` and registration and then failed every run at the execute-time contract parse (`expected string, received object`). It now parses and runs. -- **What narrows.** A blank bare string (`''` or whitespace-only) at either key is newly refused, by the shared template input's non-blank rule (`invalid_union`, with the `TYPED_EXPRESSION_SOURCE_REQUIRED.template` sentence). Before, every blank value parsed: - - `title: ''` then failed every run at the executor's guard ("notify: title is required"), so it fails either way, now earlier; - - a whitespace-only `title` passed that guard and was delivered as the notification title, and it is now refused; - - `message: ''` or a whitespace-only `message` was delivered as an empty or blank body, and it is now refused. - - The fix is to write the text, or to delete the key (`message` is optional). -- **Parse output.** `NotifyConfigSchema.parse(...).title` and `.message` go from `string` to `{ dialect: 'template', source }`, for both spellings, because the parse normalizes a bare string to that envelope. The exported `NotifyConfigParsed` type changes with them. Code that reads parse output reads `.source`. The `notify` executor, the one reader in this repo, now does, so both spellings of one text deliver the same `payload.title` and `payload.body`, and a bare string renders exactly what it rendered before. -- **Still refused, with a new sentence.** A value that is neither a string nor a template envelope (a number, an array, a `cel` envelope) was refused before (`invalid_type`). It is refused now as `invalid_union`, with the `TYPED_EXPRESSION_DIALECT_ONLY.template` sentence. -- **New, notify-only.** A template envelope on either key must carry a non-blank `source`. The executor renders `source` and has nothing to render from `ast` alone, so such an envelope is refused at the key instead of failing every run (`title`) or sending an empty body (`message`). An envelope never parsed at these keys before, so this refuses nothing that used to parse. -- **Placeholder spelling.** These two slots read the flow's single-brace `{token}` (`{record.name}`). A `{{var}}` is not a placeholder here: the inner `{var}` resolves and the outer braces stay in the text, for a bare string and an envelope alike. The `.describe()` on both keys now says so, and no longer says the text is "sent verbatim". -- `@objectstack/service-automation`: the `notify` executor reads `source` from the two template slots, and the descriptor's `title` / `message` descriptions state the `{token}` interpolation in place of "sent verbatim". diff --git a/.changeset/22057-bound-row-not-under-shared-bare-name.md b/.changeset/22057-bound-row-not-under-shared-bare-name.md deleted file mode 100644 index 881697e0a7b..00000000000 --- a/.changeset/22057-bound-row-not-under-shared-bare-name.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -'@objectstack/metadata-protocol': patch ---- - -A by-name view read naming a package now serves that package's view when another package's stored row has the same name - -Clause-②: no - -- **What was wrong.** Two installed packages can ship a view of one name, and a stored `sys_metadata` view row can be bound to one of them. The registry hydration registered that row under the view's bare name. The registry answers the bare name ahead of every package's own entry, whichever package the read names. So `getMetaItem` naming the OTHER package found no row of its own and served this row's body at its registry step, under the other package's envelope. That is the read behind `GET /api/v1/meta/view/NAME?package=…`. Meanwhile the list's slot for that package served its own view. This was measured on an unscoped kernel after a save, and on either kernel after a cold boot, because `loadMetaFromDb` hydrates through the same door on every kernel. -- **What it does now.** A view row bound to one package is not registered under a name another package ships. This is the shape the view-container expansion registration already takes. The reads answer the row from the row itself: the read naming its own package, the read naming no package, and the list's slot for that package. The read naming the other package serves that package's own view and envelope. The delete's registry heal also stops re-registering a metadata-service view baseline bound to one package under such a name. -- **Scope: `view` only.** Every other type registers as before: a row bound to one package, of a name two packages ship, keeps the bare entry with its own body and its own package's envelope. A package-less view row, and a view row of a name that only its own package ships or that no package ships, also register as before. The environment-scoped kernel's answer after a save is unchanged, because it registers nothing on a save. -- ⛔ No public export, signature, schema or accept-set change. Nothing is accepted or refused differently. The built entry declarations gain one `private` member name on `ObjectStackProtocolImplementation`. diff --git a/.changeset/22062-email-template-boot-sweep.md b/.changeset/22062-email-template-boot-sweep.md deleted file mode 100644 index 2a094c70c53..00000000000 --- a/.changeset/22062-email-template-boot-sweep.md +++ /dev/null @@ -1,27 +0,0 @@ ---- -'@objectstack/plugin-email': patch ---- - -The declared-email-template boot sweep reads the stored rows in bulk and no longer rewrites a template that has not changed - -Clause-②: no - -Every boot used to look each effective email template up on its own and rewrite its -`sys_email_template` row unconditionally: one lookup, one UPDATE and the engine's two read-backs -per template, whether or not anything had changed. Measured on `ObjectQL` over `SqlDriver` -(better-sqlite3), a steady boot over 450 unchanged templates sent 1,800 statements; it now sends 3 -reads and no write (79 templates: 316 statements, now 1). - -- The sweep reads the stored rows for the declared names in `$in` pages of 200 names. Each - `(name, locale)` takes the first row the read returns, in the order the driver gives the - per-template lookup, so a slot several organizations hold resolves to the same row as before. -- A template the bulk read did not answer (new since the last boot, a read cut short at its row - bound, or a failed read) is looked up on its own before anything is inserted, as before. -- A `managed_by: 'package'` row that already holds every column the template projects is not - rewritten. `upsertDeclaredEmailTemplate` now returns `false` for it, and - `bootstrapDeclaredEmailTemplates` counts it under `skipped`, the value both already document for - a row deliberately not written. A row with any other provenance is handled exactly as before: - admin-owned and customized rows are never written, and a legacy row with no `managed_by` is - rewritten and adopted. - -An org-scoped template edit still survives the next boot. Signatures and accepted input are unchanged. diff --git a/.changeset/22067-seed-claim-skip-automations.md b/.changeset/22067-seed-claim-skip-automations.md deleted file mode 100644 index 468418855a7..00000000000 --- a/.changeset/22067-seed-claim-skip-automations.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -'@objectstack/plugin-security': patch ---- - -The seed ownership claim no longer fires app hooks or record-change flows - -Clause-②: no - -On a freshly seeded database, the first sign-up promotes the first user to platform admin, and `claimSeedOwnership` hands every seeded row to that admin inside the same request. That write ran under a bare system context, so every claimed row went through the full write pipeline: hooks bound from app metadata fired, record-change flows ran, approvals opened on seeded records and notifications went to the new admin. Measured on hotcrm `56d98f7e` (17.7.0, a 354-row seed), the first sign-up took about 45 s, and in that time the claim fired 1,254 app hooks, ran 8 flows, opened 2 approvals and handed 8 emails to the transport. - -The claim's write now runs with `{ isSystem: true, skipAutomations: true }`. The seed itself is end-state data written without automation, and the claim keeps that rule for the write that completes it. - -- **App hooks no longer fire for the seed ownership claim.** No hook bound from metadata (an app's `hooks`, sandboxed bodies included) runs on the claim's owner change, and no record-change flow is dispatched for it. So the claim opens no approval and sends no notification. -- **Which claims.** Every pass of the claim: the promotion pass inside the first sign-up, and the pass that runs on `app:seeded` on every boot, the first and every later one. -- **Still runs.** Hooks that plugins register in code still run, so the claim still writes one audit row per claimed record (plugin-audit) and plugin-sharing still recomputes the grants the owner change earns. Every claimed row's `owner_id` is the admin and its `updated_at` still advances, exactly as before. The per-row hook ceiling and the paged fallback for very large objects are unchanged. -- **If an app relied on it.** An app hook or flow that reacted to the claim's owner change no longer sees it, just as it never saw the seed's own writes. None of the hooks or flows in ObjectStack's own example apps reads it. diff --git a/.changeset/22071-runtime-assets-boot-warning.md b/.changeset/22071-runtime-assets-boot-warning.md deleted file mode 100644 index 0091966d5dd..00000000000 --- a/.changeset/22071-runtime-assets-boot-warning.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/cli": patch ---- - -`os serve` (and `objectstack start`, which runs it) now says at boot when an app's branding logo or favicon will not be served. Before, the runtime assets route was skipped without a word when its directory was absent, so an artifact booted outside its project directory drew a broken logo and favicon and nothing in the boot output said why. - -Clause-②: no - -- Once the boot settles, every loaded app whose `branding.logo` or `branding.favicon` is a root path under `/runtime/assets/` that the route will not serve gets one warning line per file. The line names the apps and keys that use the file, the directory searched, and whether that directory came from `OS_RUNTIME_ASSETS_DIR` or the `assets/` default under the working directory. When the directory does not exist, the line says so and says nothing under `/runtime/assets/` is mounted for this run. -- The apps read are the ones the console is served, through the same metadata protocol read that `GET /api/v1/meta/app` answers from. Config boots and artifact boots are both covered. -- Whether a file is servable is decided by the route's own filename resolution, so the warning and the route cannot disagree. Absolute URLs, protocol-relative URLs, data URIs, relative paths and other root paths are not checked. -- The line goes through the kernel logger at `warn`. It shows in the banner's *Boot diagnostics* block, streams live at `--log-level debug` or `info`, and is hidden at `error` or `silent` like every other boot warning. -- ⛔ What `/runtime/assets/*` serves does not change. No route is added or removed, and the artifact still carries no asset files: ship the `assets/` directory beside it, or point `OS_RUNTIME_ASSETS_DIR` at the files. diff --git a/.changeset/22072-migrate-meta-relevance-predicate.md b/.changeset/22072-migrate-meta-relevance-predicate.md deleted file mode 100644 index e7b53ff9432..00000000000 --- a/.changeset/22072-migrate-meta-relevance-predicate.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -"@objectstack/spec": minor -"@objectstack/cli": minor ---- - -`os migrate meta` stops listing semantic notices whose surface the stack provably does not declare. It counts them instead, and `--all` lists them in full. - -Clause-②: yes - -- **`SemanticMigration.relevantWhen`** (`@objectstack/spec/migrations`) is a new optional field. It holds a structured question over the loaded stack, `{ kind: 'stack-declares', keys: [...] }`: does the stack declare anything under one of these top-level keys? A key's value in a `packages[].manifest` body counts the same as a top-level value. The question is closed and named. It is never free text, and it never matches against the prose of `surface`. The new types `SemanticRelevance`, `StackDeclaresRelevance` and `SemanticRelevanceKey` are exported beside `SemanticMigration`. -- **`applyMetaMigrations`** asks each entry's question of the stack it is given and of every hop checkpoint. - - **`todos` is unchanged.** `MigrationChainResult.todos` and `MigrationHopResult.todos` still hold every semantic entry of every hop crossed, whatever the stack holds, as before. - - **`absentTodos` is a new required member** of `MigrationChainResult` and `MigrationHopResult`. It names the subset of `todos` whose question answered `absent` in all of them: the same objects, in chain order. Code that only reads a chain result needs no change. Code that builds one of these two interfaces itself must now supply `absentTodos` (an empty array when nothing is proven absent). - - **Only a positive proof names an entry.** These cases answer `unknown` and leave it off `absentTodos`: - - a value the question cannot read (a function, a promise, a scalar, a getter that throws); - - a stack that is not a plain object; - - any `plugins`, `devPlugins` or `tiers` entry, since a plugin, or the platform plugins a tier preset loads, can register metadata the stack does not show. - - An entry that judges a conversion which applied an edit in the same run is never named either. -- **The first batch is 25 entries** (5 from protocol 17, 20 from protocol 18). Each one's surface lives only under named top-level stack keys: `analyticsCubes`, `apis`, `jobs`, `mappings`, `hooks`, `agents`, `tools`, `dashboards` (with `reports` and `pages` for the chart-config entry), `datasets`, `permissions` and `sharingRules`. None of them names a code door. Each entry was also checked to confirm that its acceptance criteria send the author to no stored row and no runtime door. Every other entry is never named absent, so it is listed exactly as before. -- **`os migrate meta`** lists `todos` minus `absentTodos`. After the listed notices it prints one line that counts the entries proven absent and names `--all`. A second line says that the proof covers the stack this run loaded, and not metadata a deployment stores. - - `--all` prints each of those entries in full, with the keys it was proven absent under. - - `--json` keeps `todos` whole and adds `absentTodos`, plus `hops[].absentTodos` with `--step`. - - `--step` reports each hop's listed count, and adds a `not listed` count to the hop line when that count is not zero. - - A run whose only notices are proven absent still writes `--out`. diff --git a/.changeset/22073-boot-warning-one-line-per-class.md b/.changeset/22073-boot-warning-one-line-per-class.md deleted file mode 100644 index b45306b5d99..00000000000 --- a/.changeset/22073-boot-warning-one-line-per-class.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/cli": patch -"@objectstack/plugin-auth": patch ---- - -The startup banner prints one line per warning class, each warning appears once, and a localhost boot no longer warns that OAuth is unencrypted. - -Clause-②: no - -- **One line per class.** Flows that declare a trigger but are not bound are grouped by trigger type and reason, with the flows listed: `⚠ 8 flows declare a 'schedule' trigger but are NOT bound — disabled by deployment policy — … (OS_AUTOMATION_SCHEDULED_WORK_ENABLED is unset or not truthy), so no time trigger arms …: flow_a, flow_b, …`. Before, the full reason (up to ~650 characters) printed once per flow. The banner now shows the reason's first sentence; `--log-level debug` still streams each flow's full reason. A real binding failure, or a missing trigger, keeps its own line, worded as before. -- **Printed once.** *Boot diagnostics* no longer repeats the automation plugin's per-flow `… is NOT bound` and shadowed-flow warnings, which the banner's `Flows:` list already shows. Its header counts them instead: `⚠ Boot diagnostics — 5 warnings logged during startup (8 more already listed above):`. Every other boot warning replays exactly as before. A boot that fails before the banner still replays all of them. -- **OAuth on loopback.** `OAuth is served UNENCRYPTED: …` is logged at `info` when the issuer's host is loopback (`localhost`, `*.localhost`, `127.0.0.0/8`, `::1`), so it is not shown at the default `warn` level. It stays `warn` on a private or link-local issuer. The sentence and the transport rule are unchanged. -- ⛔ Nothing you author changes. Which flows bind, the scheduled-work switch, the transport rule, the service-automation warning an embedded host reads, and every public key, export and parameter are unchanged. diff --git a/.changeset/22079-forms-slug-redirect.md b/.changeset/22079-forms-slug-redirect.md deleted file mode 100644 index 64b8dbae3bc..00000000000 --- a/.changeset/22079-forms-slug-redirect.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@objectstack/cli': minor ---- - -The path an author writes as a public form's `sharing.publicLink` now answers: `GET /forms/SLUG` redirects to the Console's public form page, `/_console/f/SLUG`, when the anonymous form door serves that slug - -Clause-②: yes (widening) - -- **What was wrong.** An app declares a public form as `sharing: { enabled: true, allowAnonymous: true, publicLink: '/forms/contact-us' }`, the spelling the platform's own examples use. The Console serves that form to an anonymous visitor at `/_console/f/contact-us`, but the path as written answered `404 ENDPOINT_NOT_FOUND`, so the link an author put on a website reached nothing. -- **What it does now.** Wherever the Console is mounted (`os serve` / `os dev` with the Console, or any host that mounts `createConsoleStaticPlugin` from `@objectstack/cli/console`), `GET /forms/SLUG` answers `302` with `Location: /_console/f/SLUG`, followed by the request's query string. It does so only when the anonymous form door, `GET /api/v1/forms/SLUG`, serves the form to the same request: the form's `sharing` has `enabled` and `allowAnonymous` set and names the slug, no other metadata layer withdraws it, and the deployment's tenancy posture lets it take an anonymous submission. The redirect asks that door in-process with the visitor's own request, so it decides nothing the door does not, and it reveals nothing the door does not. -- **Unchanged.** Every other request under `/forms/` answers exactly as before: a disabled form, a form not open to anonymous visitors, an unknown slug, a form the posture withholds, any method other than `GET` / `HEAD`, a trailing slash or a deeper path all get the same `404 ENDPOINT_NOT_FOUND`. Nothing is mounted at the root for a bare slug. The signed-in Console route `/_console/forms/NAME` is untouched. A deployment without the Console (`--no-ui`, `--no-console`, `OS_DISABLE_CONSOLE=1`, or no built Console) mounts no redirect. -- **Status and target.** The redirect is a `302`, never a permanent one, because whether a form is served can change with its sharing. The `Location` path is built from the Console path and the slug, encoded as one path segment, so no request can point it anywhere else. The request's query string is carried verbatim after that path, because the public form page seeds its fields from `?prefill_FIELD=` parameters: `/forms/contact-us?prefill_source=website` lands on `/_console/f/contact-us?prefill_source=website`. A query string can change only the query of the page the visitor lands on, never its origin or path. -- **Nothing to migrate.** No key, export or signature changes. `sharing.publicLink` keeps its meaning and its accepted spellings (`/forms/x`, `forms/x` and `x` name one slug). diff --git a/.changeset/22081-notify-refusal-single-brace.md b/.changeset/22081-notify-refusal-single-brace.md deleted file mode 100644 index 401d7d08b0f..00000000000 --- a/.changeset/22081-notify-refusal-single-brace.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/spec": patch ---- - -A notify flow node's `title` / `message` refusal now prescribes `'{record.name}'`, the single-brace spelling the notify executor reads. It used to prescribe the shared template sentence's `'{{record.name}}'`, which the build's `flow-double-brace-interpolation` rule then flagged on the same node and the notify renderer sent inside a stray pair of braces. - -Clause-②: no - -- Both slots take the same input as before: a bare, non-blank string or a `{ dialect: 'template', source }` envelope. Every value that parsed still parses, every value that was refused is still refused, with the same `invalid_union` code at the same path. Only the sentence changes. -- A blank bare string, a number, a non-template envelope and the like at `title` or `message` are refused with a sentence that names the key, prescribes `'{record.name}'` or `{ dialect: 'template', source: '{record.name}' }`, and says why: the notify executor interpolates single-brace `{token}` placeholders, and a doubled brace keeps its outer braces in the sent text. The branch issue that `formatZodIssue` and the API error mapper print beneath it carries the same sentence, so no `{{…}}` prescription reaches the author on these two keys. The build's flow judge (`FlowSchema`, flow registration, `os validate`) quotes the new sentence. -- Every other template slot keeps its sentence, which still prescribes `'{{record.name}}'`. That covers `titleFormat`, the prompt template's `system` / `user`, and any slot typed `TemplateExpressionInputSchema`. `TYPED_EXPRESSION_SOURCE_REQUIRED.template` and `TYPED_EXPRESSION_DIALECT_ONLY.template` are unchanged. -- The `tmpl` docblock no longer calls the envelope "Mustache" or shows only `{{record.x}}`. It now says which renderers read which braces: `{{record.x}}` for the formula template engine and the messaging, email and i18n renderers, `{record.x}` for a notify node's `title` / `message`, and either for `titleFormat`. The `TemplateExpressionInputSchema` docblock lists the notify slots the same way, and the generated expression reference page says a template slot's fix is written in the spelling its renderer reads. -- No export is added, removed or renamed, and no type changes. The notify slots take the same input as `TemplateExpressionInputSchema` from a constructor that stays internal to the package. diff --git a/.changeset/22088-flow-edge-unresolved-or-repeated-refused.md b/.changeset/22088-flow-edge-unresolved-or-repeated-refused.md deleted file mode 100644 index bc528b3ba54..00000000000 --- a/.changeset/22088-flow-edge-unresolved-or-repeated-refused.md +++ /dev/null @@ -1,39 +0,0 @@ ---- -'@objectstack/spec': minor ---- - -feat(spec)!: `FlowSchema` refuses an edge whose `source` or `target` names no node of its graph, and an edge that repeats an earlier one - -Clause-②: no (narrowing) - - - -**BREAKING**: an accept-set narrowing on a published authoring surface, shipped as `minor` under the launch-window convention for accept-set narrowings. - -**Why.** `FlowSchema` held node ids and edge ids unique, and checked nothing else about an edge. A flow whose edge named a node it no longer held (`start → node_1` over nodes `[start, end]`), or that held `start → node_1` three times, passed `FlowSchema.parse`, `objectstack validate` and the metadata save door, and publish answered 200 with `_diagnostics.valid: true`. Then it ran. The dangling edge carried the run nowhere, silently, and the repeated edge ran its target once per copy: one record update created three identical records. The Studio flow designer produced both shapes after a node was removed. - -**What is refused.** Both rules run in the region walk the node-id rule already uses, at every depth it reaches. The issue's `code` is `custom`, and each issue is anchored on the edge to fix: - -- **An endpoint that names no node of the edge's own graph**, at `edges.N.source` / `edges.N.target`. For an edge inside a `loop` / `parallel` / `try_catch` region the path is the region path, such as `nodes.N.config.body.edges.M.target`. The graph is the flow's own `nodes` for a top-level edge, and the region body's `nodes` for a region edge, because the engine resolves an endpoint there alone. So a top-level edge into a region node is refused too, and the message names the graph the node does live in. The node-id space is still one across the flow for uniqueness. -- **A repeated edge**, at `edges.N`, naming the earlier copy. Repeated means the key the engine selects on: the same `source`, `target`, `type`, `condition` (its dialect and source, so a bare CEL string and its envelope are one condition) and branch `label`. An `isDefault` copy of an unconditional edge is a repeat. A repeat is judged only between edges whose endpoints both resolve. - -That covers `FlowSchema`, `defineFlow`, `defineStack` (`STACK_SCHEMA_INVALID`, 422), `os validate`, `os compile`, an artifact's parse, `AutomationEngine.registerFlow` (which parses first) and the metadata save door (`422 INVALID_METADATA`, in draft and in publish mode). - -**What is still accepted, byte for byte.** Two nodes joined by edges the engine tells apart: different conditions, a `fault` edge beside a default one, or `approve` and `reject` branch labels into one node. Every flow whose edges all resolve in their own graph and repeat nothing. - -## FROM → TO - -| you wrote | write instead | -|:--|:--| -| an edge into a node that is not in the same `nodes` list (`target: 'node_1'`, no `node_1`) | point it at the node it was meant to reach, or delete the edge | -| a top-level edge into a node inside a region body | an edge into the region's container node; the region's own edges reach the nodes inside it | -| the same `source` → `target` edge twice, with the same `type`, `condition` and `label` | one edge. Delete the later copy; an edge meant to take its own route needs its own `condition` or branch `label` | - -**The one-line fix: delete the edge the refusal names, or re-point its endpoint.** Deleting a dangling edge changes nothing a run did, with one exception. A conditioned edge into a missing node still counted as the branch taken when its condition held, so where a flow relied on that, point the edge at a node that ends the branch. Deleting a repeated copy runs its target once per traversal instead of once per copy, which is the defect being removed. - -**Who is affected, measured.** At `aa71c4d9d`, every flow the examples ship (`app-showcase`, `app-crm`, `app-todo`: 35 flows, 55 graphs counting region bodies, 131 edges) has no dangling endpoint and no edge pair sharing a `source` and `target` at all. The flows the packages ship (the `os generate` and `os explain` templates, the new-flow seed, the `@objectstack/verify` fixture) and the platform test checklist's flow bodies are clean by reading. The CLI's golden eval corpus carries no flow. Deployed metadata and other repositories were not measured. Where such an edge already sits in a stored flow, the whole flow is refused at registration: at boot it is skipped with a warn naming it, its trigger not armed, while the flows beside it register. - -### The kit - -- **The refusal.** Two blocks in `FlowSchema`'s `superRefine`, after the edge-id rule, over `collectFlowGraphs`. No new error code: the issue is the same `custom` issue the id rules raise. -- **The ledger.** The D3 semantic entry `flow-edge-unresolved-or-repeated-refused` (protocol 18) and its step-18 rationale fragment. No key is removed, so there is no tombstone, and there is no D2 conversion: a dangling endpoint carries no intent a rewrite could recover, and dropping a copy changes how often its target runs. diff --git a/.changeset/22090-metadata-protocol-revert-stored-package.md b/.changeset/22090-metadata-protocol-revert-stored-package.md deleted file mode 100644 index 58bdb4e7dc3..00000000000 --- a/.changeset/22090-metadata-protocol-revert-stored-package.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -"@objectstack/metadata-protocol": minor ---- - -`ObjectStackProtocolImplementation.revertStoredPackage({ packageId, organizationId?, actor? })` reverts a package's stored members to their published version - -Clause-②: yes (widening) - -- A Studio-authored package's members are `sys_metadata` rows bound by `package_id`. The method reads them through the same predicate `listDrafts` uses, which now has one statement shared by both reads. -- No stored row bound to the package: it answers `{ stored: false, discarded: [] }` and touches nothing. The package's members, if it has any, belong to the metadata service. An unprovisioned `sys_metadata` answers the same way. -- Stored rows, none of them published: it refuses with `RESOURCE_CONFLICT` / 409, "Package '…' has never been published, so there is no published version to revert to." Nothing is touched. -- Otherwise it removes every draft of the package, each in the scope it lives in, through the same per-draft step `discardPackageDrafts` takes. Each item then serves its published (active) row again. An item created after the last publish has no published row and is removed. A refused draft fails the call with that refusal's own code and status. -- The only change to the public surface is this one added method. `discardPackageDrafts` and `listDrafts` answer as before. diff --git a/.changeset/22090-runtime-revert-studio-package.md b/.changeset/22090-runtime-revert-studio-package.md deleted file mode 100644 index ba8455c541d..00000000000 --- a/.changeset/22090-runtime-revert-studio-package.md +++ /dev/null @@ -1,12 +0,0 @@ ---- -"@objectstack/runtime": patch ---- - -`POST /packages/:id/revert` reverts a Studio-authored package instead of answering 404 "No metadata items found" - -Clause-②: no - -- The door asked only the metadata service, whose in-memory registry never holds a Studio package's stored rows. So a package with published items and a pending draft answered `404 RESOURCE_NOT_FOUND`, and the draft stayed. -- The door now asks the protocol's `revertStoredPackage` first, with the caller's active organization. A package with stored rows answers `200 { success: true }` with its drafts removed, or `409 RESOURCE_CONFLICT` when it has never been published. -- A package with no stored row is answered by the metadata service's `revertPackage`, exactly as before. That covers a code-shipped package and an unknown id (`404`). It is also what happens when the protocol does not provide `revertStoredPackage`. -- The request and the response shape are unchanged. diff --git a/.changeset/22093-author-strings-internal-refs.md b/.changeset/22093-author-strings-internal-refs.md deleted file mode 100644 index 567d54fce11..00000000000 --- a/.changeset/22093-author-strings-internal-refs.md +++ /dev/null @@ -1,32 +0,0 @@ ---- -'@objectstack/spec': patch -'@objectstack/service-automation': patch -'@objectstack/platform-objects': patch ---- - -Form help and refusals an author reads no longer carry service-interface names, ruling dates or another product's ids - -Clause-②: no - -Wording only: no schema, key, type, export or error-code change. - -- The notify node's Template help (the `NotifyConfigSchema.template` describe and the Studio - inspector's copy in `@objectstack/service-automation`) names the deployment's default locale in - product words instead of `II18nService.getDefaultLocale()`, and drops its ruling date. -- `MANIFEST_ID_EXAMPLES` is now `com.acme.crm` and `org.example.help-desk` (was `com.steedos.crm` - and `org.apache.superset`). The package-id refusal opens with the headline - "Invalid package id 'VALUE'." and names the key in the sentence after it, so the headline alone - carries no JSON path; the rule, the examples and the suggestion follow unchanged in substance. A - caller that matched the old "on KEY. Expected reverse-domain notation" wording matches the - headline, or compares against `manifestIdRefusal()` by reference, instead. -- Ruling dates leave the describes Studio renders as form help: field `required` and `multiple`, - form-view field and section `visibleWhen`, section `collapsible` / `collapsed`, the redirect - arm's `submitBehavior.url`, and page `kind` / `source` (the ADR citations stay). They also - leave the refusals for padded grouping field names, `submitBehavior.url`, `features.*` in a - form-view predicate, and the four filter comparand refusals (null ordering comparand, - `{ $field }` in a list position, null list member, blank `$between` bound). Each sentence still - states the rule, why it exists and the repair. -- The email-template form's Identity section help says how senders address a template instead of - naming `IEmailService.sendTemplate`, in all four shipped locales. -- The action `description` help no longer ends in a dangling dash left behind by an earlier - strip: "(one dialog, not two —)" now reads "(one dialog, not two)". diff --git a/.changeset/22116-migrate-meta-out-snapshot.md b/.changeset/22116-migrate-meta-out-snapshot.md deleted file mode 100644 index 6e197546ac2..00000000000 --- a/.changeset/22116-migrate-meta-out-snapshot.md +++ /dev/null @@ -1,14 +0,0 @@ ---- -"@objectstack/cli": patch ---- - -fix(cli): `os migrate meta --out FILE` writes its snapshot on a range that crosses no step (#22116) - -Clause-②: no - -`os migrate meta --from 18 --to 18 --out FILE` exited 0, printed no snapshot line and wrote no `FILE`. The same command with `--json` wrote `FILE`. The human report returned early on a run with nothing to migrate, and that return came before the `--out` write. An operator or a CI step that keeps `FILE` as the record of the run then found no file, or read an earlier run's file as this one's. - -- The human mode now writes `FILE` and prints the line that names it on every run. On a run with nothing to migrate, the line comes after the range answer, before `--write`'s outcome and the data migrations, the same order as on every other run. -- The bytes are the ones `--json` writes for the same run: the stack the chain returned, which for a range with no step is the stack as loaded. -- The fix sits in the branch both "nothing to migrate" answers share. A range with steps that applies and lists nothing takes the same branch, but no range reaches it on this build, because every major carries semantic notices and the chain lists them all. -- Unchanged: the `--json` mode, `--write`, `--stored` and the chain. A range with steps writes `FILE` exactly as before. diff --git a/.changeset/9139-cbp-master-detail-required-error.md b/.changeset/9139-cbp-master-detail-required-error.md deleted file mode 100644 index a47929108e0..00000000000 --- a/.changeset/9139-cbp-master-detail-required-error.md +++ /dev/null @@ -1,33 +0,0 @@ ---- -"@objectstack/lint": minor -"@objectstack/spec": minor ---- - -feat(lint)!: `relationship/master-detail-required` refuses the three unsafe master-reference shapes at `error` on a `controlled_by_parent` object (#9139) - -Clause-②: no (narrowing) - -A `controlled_by_parent` detail derives all of its record access from the master its `master_detail` reference names (ADR-0055). Three declarable shapes of that reference leave the security gate as the only thing refusing a detail record saved without its master, because record validation never checks a field that is not `required` and skips `readonly` and `system` fields before its required check: - -1. `required` absent, or `required: false`; -2. `required: true` with `readonly: true`; -3. `required: true` with `system: true`. - -A record that lands without its master anyway is readable by nobody, and every later write to it by id is refused. Until now `relationship/master-detail-required` was a `warning` with the predicate "`required` is not `true`", on every object, so shapes 2 and 3 drew no finding at any severity. The maintainer ruling of 2026-08-16 (Direction 1) scheduled the promotion for the v18 boundary, scoped to `controlled_by_parent`. - -**BREAKING — what moves for consumers.** - -- `os lint` reports each of the three shapes at `error` when the object declares `sharingModel: 'controlled_by_parent'`, located at the defect (`…fields.FIELD.required`, `.readonly` or `.system`). It covers every `master_detail` field of such an object, the same scope the builder's `required: true` force already applies. `os lint` therefore exits non-zero on such a stack, and the metadata-generation rubric (`scoreMetadata`) weighs the finding as an error and marks the stack `valid: false`. -- `@objectstack/spec` gains the step-18 semantic migration entry `cbp-master-detail-required-lint-error`, so `os migrate meta` across protocol 18 prints the prescription below. - -**Remedy — the v18 upgrade-checklist line.** On every object with `sharingModel: 'controlled_by_parent'`, give each `master_detail` reference `required: true` and remove any `readonly: true` or `system: true` from it. `os lint` now refuses the missing-`required`, `required` + `readonly` and `required` + `system` shapes there at `error` (`relationship/master-detail-required`). An object authored through `ObjectSchema.create` already gets `required: true` when the key is omitted, so the edit there is dropping the flag. - -**Unchanged.** - -- On every object that is not `controlled_by_parent` the rule is exactly as before: a `warning` for a `master_detail` without `required: true`, the same message and fix, and no finding for the two flagged shapes. -- The rule is not in the authoring-rule registry. `os build`, `os validate` and the metadata save door do not run it, so a stack carrying one of the shapes still builds and publishes. Only `os lint`'s exit code and the generation rubric move. -- Runtime is untouched. The security gate keeps refusing an insert that omits the master FK on these shapes and keeps resolving the master for metadata already at rest, and stored metadata is neither rewritten nor refused on load. -- No export or signature moves in either package. -- Measured before crossing, at `b04a5295f`: 129 authored objects across the example apps, the platform, plugin and service objects and the CLI's golden eval corpus. 7 of them are `controlled_by_parent`, and 0 draw the new `error`. - - diff --git a/.changeset/9591-migrate-meta-write.md b/.changeset/9591-migrate-meta-write.md deleted file mode 100644 index a6bb673ec1d..00000000000 --- a/.changeset/9591-migrate-meta-write.md +++ /dev/null @@ -1,15 +0,0 @@ ---- -"@objectstack/cli": minor ---- - -`os migrate meta --write` writes the chain's mechanical changes into the authored source files, in place, at every site it can prove - -Clause-②: yes (widening) - -- A new flag on the authored-source mode: `os migrate meta --from N --write`. Without it nothing changes: the dry run, its report and its `--json` payload are what they were, and `--out` still writes its snapshot. -- What it writes: each mechanical change the chain applied (`applied`), at a site it traces to one object or array literal in one project file — through `define*` calls and the `.create(…)` factories `@objectstack/spec` exports, module-level `const` bindings, relative imports and re-exports, and `Object.values()` over a namespace import — when the loaded value matches that literal and nothing else references the bindings on the way. Only that site's bytes change: a renamed key keeps its value and its comments, a removed key takes its own line(s), and every other byte (comments, formatting, key order) stays as it was. -- What it refuses, each change listed with the reason (`--json`: `write.manual[].kind`): `computed`, `helper`, `spread`, `shared`, `outside-project`, `mismatch`, `injected`, `unspellable`, `layout` and `unattributed`; and `entangled`, because a conversion's edits are written whole or not at all. -- What it never writes: the semantic changes (`todos`), which stay listed exactly as before, and a site a conversion declines, for which no mechanical change exists. -- After writing it re-runs the chain over the written sources. Unless the re-run applies exactly the changes it left, it restores every file it wrote and exits 1. -- `--json` gains a `write` key, only with `--write`: `status`, `files`, `written`, `manual`, `unexplained` and `verification`. -- `--write` is exclusive with `--stored`; `--stored --apply` is unchanged. diff --git a/.changeset/console-a58626c88dc8.md b/.changeset/console-a58626c88dc8.md deleted file mode 100644 index 50a07af0a60..00000000000 --- a/.changeset/console-a58626c88dc8.md +++ /dev/null @@ -1,82 +0,0 @@ ---- -"@objectstack/console": minor ---- - -Console (objectui) refreshed to `a58626c88dc8`. Frontend changes in this range: - -Derived from the changesets objectui declared over the range — 36 releasing of 36 changesets added across 36 non-merge commits. - -- **minor** — The timeline's unused "Overdue" bucket label is removed: `timeline.bucket.overdue` is gone from all ten language packs and from the timeline's built-in English defaults (objectui#… (objectui `c910630cf`) -- **minor** — **BREAKING** — Inside a dashboard widget's legacy `component` envelope, a `metric-card` is now judged by the slot's component arm alone, so every key that arm refuses is refused there too, with… (objectui `89cc738da`) -- **minor** — **BREAKING** — "Recently Accessed" shows each item's own label, in the current language, and a visit that does not change the list no longer writes the `ui.recent` preference (objectui#11678). (objectui `48c82c9d5`) -- **minor** — The calendar and the timeline start the week on the first day of the week of the user's locale (objectui#11675), instead of a fixed Sunday (calendar) and a fixed Monday (timeline)… (objectui `9ca3cacbe`) -- **minor** — The console's settings pages resolve icons through the shared `getLazyIcon` helper and no longer log `[lucide-react]: Name in Lucide DynamicIcon not found` (objectui#11679). (objectui `abd374bb2`) -- **minor** — `DefaultLoginPage` and `DefaultRegisterPage` offer a generic sign-up only where the server would accept one (objectui#11705). Under the default `invite_only` audience posture the… (objectui `7e2d5b0ee`) -- **minor** — Objects with a picklist-bound field save again from the OWD overview, the Setup fields and objects pages, the metadata-admin embedded-item editor, and `MetadataService.saveFields`… (objectui `da3545375`) -- **minor** — The keyboard-shortcuts dialog (`?`) lists only shortcuts that do something (objectui#11674). (objectui `de96f3d0c`) -- **minor** — **BREAKING** — A `metric-card` placed in a dashboard's widget slot refuses `label` by name and points at `title`, the key its heading is drawn from (objectui#4425). (objectui `eb4552e71`) -- **minor** — The console's login and register pages offer a generic sign-up only where the server would accept one (objectui#11691). `/api/v1/auth/config` states the sign-up rule as two keys,… (objectui `daa7caff4`) -- **minor** — An object-bound timeline no longer heads every past date "Overdue": a day before today goes under a neutral "Earlier" bucket, translated in every language (objectui#11676). (objectui `ce464d958`) -- **minor** — Studio reads shared picklists: a select field can use one, an object with a picklist-bound field saves again, and the picklist page is read-only (objectui#10202, the objectui half… (objectui `c3623eb11`) -- **minor** — The AI chat's tool-approval card and its "Open in Builder" handoff card are translated in every language (objectui#11667). (objectui `57d82cb34`) -- **minor** — The chat launchers show a marker while a proposed plan awaits the user's approval (objectui#11666, item 6 of objectui#2458). A user who closed the chat with a blueprint still wait… (objectui `848ba0e12`) -- **minor** — Five fixes on the AI build surface, from the 2026-10-05 cloud acceptance run (objectui#11658). (objectui `fc3c2cc1c`) -- **minor** — `action:group` and `action:menu` no longer read a member's `properties.params` (objectui#11638). A container member's `properties.params` no longer reaches the action runner. (objectui `73b5d7764`) -- **minor** — `LoginForm` shows its "Don't have an account? Sign up" row only when the caller passes `registerUrl` (objectui#11634). The prop used to default to `'/register'`, so a caller that… (objectui `f1a177c41`) -- **minor** — A field group's `visibleWhen` now gates its section on the record detail page, the same way it gates the section on the entry form (objectui#11630). Take an object whose `fieldGro… (objectui `76993f8ef`) -- **patch** — fix(plugin-tree): tree cells draw the same field faces as list cells, so a boolean no longer shows as `true` (objectui#11686) (objectui `a58626c88`) -- **patch** — **The record header's highlight chips share the row's free width and truncate only when it runs out (objectui#11684).** A Product with SKU "QA Widget 1" read "QA Wid…" in the reco… (objectui `7300fcafe`) -- **patch** — An authored `view:calendar` or `view:timeline` node loads its plugin and renders the calendar or the timeline, and a console boot no longer logs the registry's race warning for th… (objectui `e6dcd85cc`) -- **patch** — The approvals inbox's request drawer shows its record summary the way the record page shows the record (objectui#11677). (objectui `3409fe89e`) -- **patch** — The flow designer saves a screen field's `Min` and `Max` as numbers (objectui#11664). (objectui `5ba255538`) -- **patch** — The metric cards no longer write authored keys they do not read onto the page (objectui#4425). Rendered through `SchemaRenderer`, which is how every dashboard draws its KPI tiles… (objectui `a600924f2`) -- **patch** — Console, record page and Studio copy from the 2026-10-05 cloud acceptance run (objectui#11659). (objectui `f9f4a62d5`) -- **patch** — The flow designer's SLA escalation switch on an approval node no longer writes a refused `escalation: { enabled: false }` block, and switching it off keeps the values the author e… (objectui `9f3ed7bca`) -- **patch** — Cancelling a background import shows the rows the server already committed, with Undo (objectui#11650). `ImportWizard`'s Cancel used to show "Import cancelled · 0 imported" withou… (objectui `846f98251`) -- **patch** — Drag-to-reorder works on a grouped sidebar menu, within each level (objectui#11626). `NavigationRenderer` had a sortable path only in its group-free arm. Every stock app's menu is… (objectui `b88937b65`) -- **patch** — The `object-kanban` board totals the view's `summarizeField` in each column header (objectui#11629). (objectui `f4370f426`) -- **patch** — On an object that declares no list view, a grid toolbar change no longer sends a save the door refuses (objectui#11643). Such an object opens on the "All Records" tab the console… (objectui `43671468d`) -- **patch** — The console's `/verify-email?token=…` page verifies the address again (objectui#11633). It used to send the token as `POST /api/v1/auth/verify-email` with a JSON body. better-auth… (objectui `8057a8b14`) -- **patch** — On a runtime with no marketplace that still mounts install-local (an offline boot, `OS_CLOUD_URL=off`), a local install's Details page now offers its local menu: re-seed sample da… (objectui `0baf86f1b`) -- **patch** — A dataset-bound dashboard widget names its comparison window from `compareTo.kind` (objectui#11632). `previousPeriod` reads "vs previous period" and `previousYear` reads "vs last… (objectui `e398a54f0`) -- **patch** — Two grid toolbar changes to one view in one session now both survive a reload (objectui#11642). Before, the second change overwrote the first: changing density and then sorting by… (objectui `531b26c8f`) -- **patch** — A lookup whose `dependsOn` names a parent field drops its selection when that parent changes or is cleared (objectui#11631). (objectui `c00039842`) -- **patch** — A grid toolbar change on a served view is stored where the save door keeps it, so it survives a reload (objectui#11625). This covers density, a column-header sort and the hide-fie… (objectui `59917c4b2`) - -⚠️ 3 of these carry a breaking change: 3 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record. - - - -objectui range: `0abd4f9f8769...a58626c88dc8` diff --git a/.changeset/objectui-pin-citations-a58626c88dc8.md b/.changeset/objectui-pin-citations-a58626c88dc8.md deleted file mode 100644 index 2d206d29259..00000000000 --- a/.changeset/objectui-pin-citations-a58626c88dc8.md +++ /dev/null @@ -1,13 +0,0 @@ ---- -'@objectstack/spec': patch ---- - -The spec's objectui citations, and the shipped description text that names the `.objectui-sha` pin (the `FormField.span` describe and six migration-entry descriptions), are re-measured against the new console pin, objectui `a58626c88dc8`. - -Clause-②: no - -Every anchor was mapped through the objectui diff `0abd4f9f8769..a58626c88dc8`, 252 paths over 36 commits. Fifteen of those paths are files an asserting record cites: `ObjectKanban.tsx`, `KanbanImpl.tsx`, `ObjectTree.tsx`, `ObjectTimeline.tsx`, `record-details.tsx`, `action-group.tsx`, `action-menu.tsx`, `static-params.ts`, `MetricWidget.tsx`, `MetricCard.tsx`, `form.tsx`, `plugin-kanban.mdx` and the `en` / `zh` / `de` packs. In each, every cited line is byte-identical at the new pin, so each anchor that moved was re-pointed to its new line and the record says by how much; no read point an asserting record cites changed content or died. Every other cited file is byte-identical across the hop (`git diff --quiet`). The seven quoted anchor lines verify against objectui at the new pin. Three records carry a count, and each count was re-taken by its record's own method with the same reading: the `keyboardNavigation` hit lines (15, against 3 for the `schema.editable` control), `ObjectKanban.tsx`'s `quickAdd` / `onQuickAdd` (2 each, against 11 for `onCardClick`), and the `ElementDataSourceGate` occurrences in five `src/index.tsx` shells (0, 3, 3, 3 and 4). - -The six migration entries' corpus counts were re-taken with `git grep -o -F`, the method that first reproduced every `0abd4f9f8769` number. The corpus is now 7754 tracked files. All 99 checked tokens (the export lists of `plugin-lifecycle-advanced.zod.ts`, `tracing.zod.ts` and `metrics.zod.ts`, plus every named key) still read zero, except `Span` / `SpanSchema`, which read 509 / 57: the one new `Span` hit is a `colSpan`. - -No key, default, enum member or export moves. diff --git a/content/docs/deployment/self-hosting.mdx b/content/docs/deployment/self-hosting.mdx index 4c0ac7e5345..a14bd89e252 100644 --- a/content/docs/deployment/self-hosting.mdx +++ b/content/docs/deployment/self-hosting.mdx @@ -75,7 +75,7 @@ docker run -p 8080:8080 \ -e OS_DATABASE_URL="postgres://user:pass@db-host:5432/myapp" \ -e OS_AUTH_SECRET \ -e OS_SECRET_KEY \ - ghcr.io/objectstack-ai/objectstack:17.7.0 + ghcr.io/objectstack-ai/objectstack:17.8.0 ``` (`OS_ARTIFACT_PATH` also accepts an `https://` URL, so the artifact can come @@ -93,7 +93,7 @@ docker run -p 8080:8080 \ -e OS_ARTIFACT_URL="https://releases.example.com/hotcrm-2.2.2.json#sha256=<64 hex chars>" \ -e OS_DATABASE_URL="postgres://user:pass@db-host:5432/myapp" \ -e OS_AUTH_SECRET -e OS_SECRET_KEY \ - ghcr.io/objectstack-ai/objectstack:17.7.0 + ghcr.io/objectstack-ai/objectstack:17.8.0 ``` Both schemes work: `https://…` is fetched at boot, `file:///…` is read directly @@ -144,7 +144,7 @@ COPY . . RUN npx os build # → dist/objectstack.json # ── Runtime: the official ObjectStack runtime image ────────────────── -FROM ghcr.io/objectstack-ai/objectstack:17.7.0 +FROM ghcr.io/objectstack-ai/objectstack:17.8.0 COPY --from=build --chown=node:node /app/dist/objectstack.json /srv/app/objectstack.json ``` @@ -162,7 +162,7 @@ image)? The official image is nothing more than: ```dockerfile title="Dockerfile (self-built runtime, equivalent)" FROM node:22-slim -RUN npm install -g @objectstack/cli@17.7.0 +RUN npm install -g @objectstack/cli@17.8.0 WORKDIR /srv/app RUN chown node:node /srv/app diff --git a/content/docs/releases/index.mdx b/content/docs/releases/index.mdx index 4946b43bce2..1c214e8035a 100644 --- a/content/docs/releases/index.mdx +++ b/content/docs/releases/index.mdx @@ -18,7 +18,7 @@ migration steps, then covers new capabilities and notable fixes. ## Versions -- [v17.0.0](/docs/releases/v17) — Files become owned `sys_file` records with server-enforced `accept`/`maxSize` and a governed download path, bulk export becomes its own opt-in privilege, the SDK is reconciled against the routes the server actually mounts (21 dead methods out, 40+ real ones in), approval nodes route approvers dynamically via CEL expressions and decision outputs, a datasource that cannot connect fails the boot, and Node 22 becomes the supported floor; 17.1 adds partial field masking, record-view auditing on `sys_audit_log`, and a per-object read-only approval visibility tier — and makes a deactivated permission set or position actually stop granting access, withdraws the bulk-export wildcard from the shipped admin sets, and gives all three flow doors one honest HTTP status table; 17.2 tightens by-id `update`/`delete` against a silently-dropped `where` predicate or a mismatched id, retires `sys_position.permissions` and other dead ADR-0049 surfaces, and stops analytics from answering the wrong number on a cross-object filter (current series: 17.7.0, released 2026-10-06). +- [v17.0.0](/docs/releases/v17) — Files become owned `sys_file` records with server-enforced `accept`/`maxSize` and a governed download path, bulk export becomes its own opt-in privilege, the SDK is reconciled against the routes the server actually mounts (21 dead methods out, 40+ real ones in), approval nodes route approvers dynamically via CEL expressions and decision outputs, a datasource that cannot connect fails the boot, and Node 22 becomes the supported floor; 17.1 adds partial field masking, record-view auditing on `sys_audit_log`, and a per-object read-only approval visibility tier — and makes a deactivated permission set or position actually stop granting access, withdraws the bulk-export wildcard from the shipped admin sets, and gives all three flow doors one honest HTTP status table; 17.2 tightens by-id `update`/`delete` against a silently-dropped `where` predicate or a mismatched id, retires `sys_position.permissions` and other dead ADR-0049 surfaces, and stops analytics from answering the wrong number on a cross-object filter (current series: 17.8.0, released 2026-10-08). - [v16.0.0](/docs/releases/v16) — One org identifier (`organizationId`) across hooks and actions, quorum + per-group sign-off (会签) approvals with metadata-declared decision actions, time-relative automations, filtered roll-ups, strict dashboard widgets, an identity-scoped MCP stdio transport, and a platform-wide enforce-or-remove sweep that makes dead metadata loud; 16.1 adds a `requires` capability-provider preflight, two more dashboard build gates, and `runAs:'user'` automations that run with the triggering user's real grants (final release: 16.1.0). - [v15.0.0](/docs/releases/v15) — Explain record access layer by layer, a docked AI workspace in the Console, project-ready Gantt charts, and phone sign-in; 15.1 adds permission-following attachments, no-code third-party connectors, dashboard-wide filters, pinyin search, and whole-record inline editing — with materially safer multi-tenant and write-path defaults (final release: 15.1.1). - [v14.0.0](/docs/releases/v14) — ADR-0090 vocabulary convergence completed, object `enable.*` flags become real gates, admin user management, phone/SMS auth, book-audience enforcement, data-lifecycle contract, and effective-dated grants (final release: 14.8.0). diff --git a/content/docs/upgrading.mdx b/content/docs/upgrading.mdx index 56282af922c..680fc169ac4 100644 --- a/content/docs/upgrading.mdx +++ b/content/docs/upgrading.mdx @@ -52,7 +52,7 @@ The official image is `ghcr.io/objectstack-ai/objectstack`, and its tags mirror ```bash # docker-compose.yml, or your orchestrator's manifest -image: ghcr.io/objectstack-ai/objectstack:17.7.0 +image: ghcr.io/objectstack-ai/objectstack:17.8.0 ``` On a host running the artifact directly under systemd, the same move is a file diff --git a/docker/README.md b/docker/README.md index c5dfc558e5d..18f84990e1a 100644 --- a/docker/README.md +++ b/docker/README.md @@ -29,7 +29,7 @@ Multi-arch: `linux/amd64` + `linux/arm64`. [Self-Hosted Deployment](https://objectstack.ai/docs/deployment/self-hosting)): ```dockerfile -FROM ghcr.io/objectstack-ai/objectstack:17.7.0 +FROM ghcr.io/objectstack-ai/objectstack:17.8.0 COPY --chown=node:node dist/objectstack.json /srv/app/objectstack.json ``` @@ -40,7 +40,7 @@ docker run -p 8080:8080 \ -v "$PWD/dist/objectstack.json:/srv/app/objectstack.json:ro" \ -e OS_DATABASE_URL="postgres://user:pass@db-host:5432/myapp" \ -e OS_AUTH_SECRET -e OS_SECRET_KEY \ - ghcr.io/objectstack-ai/objectstack:17.7.0 + ghcr.io/objectstack-ai/objectstack:17.8.0 ``` `OS_ARTIFACT_PATH` also accepts an `https://` URL, so the artifact can come @@ -72,7 +72,7 @@ for a `file:…` path — one box only, wrong for multi-node) and MongoDB (`libsql://…` / Turso). Add one by extending the image: ```dockerfile -FROM ghcr.io/objectstack-ai/objectstack:17.7.0 +FROM ghcr.io/objectstack-ai/objectstack:17.8.0 USER root RUN npm install -g tedious USER node @@ -100,5 +100,5 @@ reverse-proxy / multi-node guidance: ## Local build of this image ```bash -docker build -t objectstack:dev --build-arg OS_CLI_VERSION=17.7.0 docker/ +docker build -t objectstack:dev --build-arg OS_CLI_VERSION=17.8.0 docker/ ``` diff --git a/examples/app-crm/CHANGELOG.md b/examples/app-crm/CHANGELOG.md index e806482a1fc..252ea203a54 100644 --- a/examples/app-crm/CHANGELOG.md +++ b/examples/app-crm/CHANGELOG.md @@ -1,5 +1,33 @@ # @objectstack/example-crm +## 4.0.100 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + - @objectstack/runtime@17.8.0 + - @objectstack/service-i18n@17.8.0 + ## 4.0.99 ### Patch Changes diff --git a/examples/app-crm/package.json b/examples/app-crm/package.json index 144e13b5ff2..eda88107a5f 100644 --- a/examples/app-crm/package.json +++ b/examples/app-crm/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-crm", - "version": "4.0.99", + "version": "4.0.100", "description": "Minimal CRM example \u2014 a smoke-test workspace that exercises the metadata loading pipeline (objects \u2192 views \u2192 app \u2192 dashboard \u2192 hook \u2192 flow \u2192 seed). For a full-featured enterprise CRM see https://github.com/objectstack-ai/hotcrm.", "license": "Apache-2.0", "private": true, diff --git a/examples/app-multi-package/CHANGELOG.md b/examples/app-multi-package/CHANGELOG.md index 931c0e1c5cd..f1090f8c06b 100644 --- a/examples/app-multi-package/CHANGELOG.md +++ b/examples/app-multi-package/CHANGELOG.md @@ -1,5 +1,28 @@ # @objectstack/example-multi-package +## 0.0.7 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + ## 0.0.6 ### Patch Changes diff --git a/examples/app-multi-package/package.json b/examples/app-multi-package/package.json index 9624a7ae77b..71881a6d408 100644 --- a/examples/app-multi-package/package.json +++ b/examples/app-multi-package/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-multi-package", - "version": "0.0.6", + "version": "0.0.7", "description": "One release artifact carrying TWO packages that share a namespace (ADR-0130 D4) — the producer-side fixture for `packages[]`", "license": "Apache-2.0", "private": true, diff --git a/examples/app-showcase/CHANGELOG.md b/examples/app-showcase/CHANGELOG.md index e81d88ed103..0e701837dbb 100644 --- a/examples/app-showcase/CHANGELOG.md +++ b/examples/app-showcase/CHANGELOG.md @@ -1,5 +1,42 @@ # @objectstack/example-showcase +## 0.3.22 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [8a399b2] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [8601526] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + - @objectstack/service-datasource@17.8.0 + - @objectstack/runtime@17.8.0 + - @objectstack/cloud-connection@17.8.0 + - @objectstack/connector-mcp@17.8.0 + - @objectstack/connector-openapi@17.8.0 + - @objectstack/connector-rest@17.8.0 + - @objectstack/connector-slack@17.8.0 + - @objectstack/driver-sql@17.8.0 + - @objectstack/service-i18n@17.8.0 + ## 0.3.21 ### Patch Changes diff --git a/examples/app-showcase/package.json b/examples/app-showcase/package.json index 571d3dc5a41..6312b883573 100644 --- a/examples/app-showcase/package.json +++ b/examples/app-showcase/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-showcase", - "version": "0.3.21", + "version": "0.3.22", "description": "Kitchen-sink showcase workspace — exercises every metadata type, every view type, every chart type, and the major end-to-end capability chains (security, automation, analytics). Built for demonstration, debugging, and coverage-driven verification.", "license": "Apache-2.0", "private": true, diff --git a/examples/app-todo/CHANGELOG.md b/examples/app-todo/CHANGELOG.md index 48292ae4f43..92b91b0b00e 100644 --- a/examples/app-todo/CHANGELOG.md +++ b/examples/app-todo/CHANGELOG.md @@ -1,5 +1,42 @@ # @objectstack/example-todo +## 4.0.100 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/runtime@17.8.0 + - @objectstack/client@17.8.0 + - @objectstack/driver-sqlite-wasm@17.8.0 + - @objectstack/mcp@17.8.0 + - @objectstack/metadata@17.8.0 + - @objectstack/knowledge-memory@17.8.0 + - @objectstack/service-i18n@17.8.0 + - @objectstack/service-knowledge@17.8.0 + ## 4.0.99 ### Patch Changes diff --git a/examples/app-todo/package.json b/examples/app-todo/package.json index b59cbddcb3a..2c83d9fc95f 100644 --- a/examples/app-todo/package.json +++ b/examples/app-todo/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-todo", - "version": "4.0.99", + "version": "4.0.100", "description": "Example Todo App using ObjectStack Protocol", "license": "Apache-2.0", "private": true, diff --git a/examples/embed-objectql/CHANGELOG.md b/examples/embed-objectql/CHANGELOG.md index 272a2c5ded7..4a34def06d2 100644 --- a/examples/embed-objectql/CHANGELOG.md +++ b/examples/embed-objectql/CHANGELOG.md @@ -1,5 +1,32 @@ # @objectstack/example-embed-objectql +## 0.0.40 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/driver-memory@17.8.0 + ## 0.0.39 ### Patch Changes diff --git a/examples/embed-objectql/package.json b/examples/embed-objectql/package.json index f761b2a9574..2dd5143fe77 100644 --- a/examples/embed-objectql/package.json +++ b/examples/embed-objectql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/example-embed-objectql", - "version": "0.0.39", + "version": "0.0.40", "private": true, "description": "Embed the ObjectQL engine as a plain library via @objectstack/objectql/core — no kernel, no plugins, no metadata protocol (ADR-0076).", "type": "module", diff --git a/packages/adapters/hono/CHANGELOG.md b/packages/adapters/hono/CHANGELOG.md index 0882184915b..e14eddf023b 100644 --- a/packages/adapters/hono/CHANGELOG.md +++ b/packages/adapters/hono/CHANGELOG.md @@ -1,5 +1,29 @@ # @objectstack/hono +## 17.8.0 + +### Minor Changes + +- 5cfd866: feat(sharing): a share-link password can be sent in the `X-Share-Password` header whatever its characters, under a declared encoding (`X-Share-Password-Encoding: utf-8`) + + Clause-②: yes (widening) + + - **What was missing.** A browser cannot put a character above U+00FF in a request header (`Headers` throws a `TypeError` before the request leaves), and it strips leading and trailing spaces. `createLink` accepts any password, so a link whose password has a CJK character or an emoji could not be opened through the header. + - **What is now accepted.** A new companion request header, `X-Share-Password-Encoding`, declares how `X-Share-Password` is encoded. Its one value is `utf-8`, compared case-insensitively. Under it, `X-Share-Password` carries the password's UTF-8 bytes percent-encoded, as `encodeURIComponent(password)` produces them, and both public routes (`GET /api/v1/share-links/:token/resolve` and `/:token/messages`) decode it on both mounts: the sharing plugin's routes and the runtime dispatcher's `/share-links` domain. Both read the pair through one helper, `readSharePasswordHeader`, exported from `@objectstack/types` with the header-name constants. + - **Unchanged.** Without `X-Share-Password-Encoding`, `X-Share-Password` is read raw, exactly as before, so every value a client sends today resolves as it did. That includes a Latin-1 password and a raw password containing `%`; the server never percent-decodes a value nobody declared encoded. The `?password=` query parameter is still read first, and when it is present the header pair is not read. + - **What is refused.** `X-Share-Password-Encoding` naming any other value, or a password header that is not percent-encoded UTF-8 under `utf-8` (a `%` without two hex digits, octets that are not well-formed UTF-8, a character outside visible ASCII), answers `400 VALIDATION_FAILED` before the token is looked up. It is never compared raw instead. The message names the headers and the rule, never the presented value. + - **Response headers.** Both public routes now answer `Vary: X-Share-Password, X-Share-Password-Encoding`, still beside `Cache-Control: no-store`. + - **Cross-origin clients.** `X-Share-Password-Encoding` is in the default CORS preflight allow-list (`DEFAULT_CORS_ALLOW_HEADERS` in `@objectstack/plugin-hono-server`, which the `@objectstack/hono` adapter also applies). A deployment that passes its own `allowHeaders` must add `X-Share-Password-Encoding` beside `X-Share-Password` to let a cross-origin client send an encoded password. + +### Patch Changes + +- Updated dependencies [1fb274e] +- Updated dependencies [5cfd866] +- Updated dependencies [a543e24] + - @objectstack/runtime@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/plugin-hono-server@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/adapters/hono/package.json b/packages/adapters/hono/package.json index 1b37f4b2b8e..5141795c39b 100644 --- a/packages/adapters/hono/package.json +++ b/packages/adapters/hono/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/hono", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/apps/account/CHANGELOG.md b/packages/apps/account/CHANGELOG.md index f5a62f910ee..134c4864a21 100644 --- a/packages/apps/account/CHANGELOG.md +++ b/packages/apps/account/CHANGELOG.md @@ -1,5 +1,32 @@ # @objectstack/account +## 17.8.0 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/apps/account/package.json b/packages/apps/account/package.json index 64dc5c33f74..2e1bb3395fb 100644 --- a/packages/apps/account/package.json +++ b/packages/apps/account/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/account", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "ObjectStack Account — the end-user account/self-service console app, packaged as its own ObjectStack app package (ADR-0048: one app per package).", "main": "dist/index.js", diff --git a/packages/apps/setup/CHANGELOG.md b/packages/apps/setup/CHANGELOG.md index b7d45ed6cb0..9359c085aa0 100644 --- a/packages/apps/setup/CHANGELOG.md +++ b/packages/apps/setup/CHANGELOG.md @@ -1,5 +1,32 @@ # @objectstack/setup +## 17.8.0 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/apps/setup/package.json b/packages/apps/setup/package.json index f1ef331e465..2474f543ef2 100644 --- a/packages/apps/setup/package.json +++ b/packages/apps/setup/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/setup", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "ObjectStack Setup — the platform administration app, packaged as its own ObjectStack app package (ADR-0048: one app per package).", "main": "dist/index.js", diff --git a/packages/apps/studio/CHANGELOG.md b/packages/apps/studio/CHANGELOG.md index 0e7fff77818..6017ee37343 100644 --- a/packages/apps/studio/CHANGELOG.md +++ b/packages/apps/studio/CHANGELOG.md @@ -1,5 +1,32 @@ # @objectstack/studio +## 17.8.0 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/apps/studio/package.json b/packages/apps/studio/package.json index bfeda6eb73e..68a3c61b418 100644 --- a/packages/apps/studio/package.json +++ b/packages/apps/studio/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/studio", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "ObjectStack Studio — the metadata builder app, packaged as its own ObjectStack app package (ADR-0048: one app per package).", "main": "dist/index.js", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index cb1f79f1b34..b6f5a46a5ea 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,180 @@ # @objectstack/cli +## 17.8.0 + +### Minor Changes + +- cdeabec: `os migrate meta` stops listing semantic notices whose surface the stack provably does not declare. It counts them instead, and `--all` lists them in full. + + Clause-②: yes + + - **`SemanticMigration.relevantWhen`** (`@objectstack/spec/migrations`) is a new optional field. It holds a structured question over the loaded stack, `{ kind: 'stack-declares', keys: [...] }`: does the stack declare anything under one of these top-level keys? A key's value in a `packages[].manifest` body counts the same as a top-level value. The question is closed and named. It is never free text, and it never matches against the prose of `surface`. The new types `SemanticRelevance`, `StackDeclaresRelevance` and `SemanticRelevanceKey` are exported beside `SemanticMigration`. + - **`applyMetaMigrations`** asks each entry's question of the stack it is given and of every hop checkpoint. + - **`todos` is unchanged.** `MigrationChainResult.todos` and `MigrationHopResult.todos` still hold every semantic entry of every hop crossed, whatever the stack holds, as before. + - **`absentTodos` is a new required member** of `MigrationChainResult` and `MigrationHopResult`. It names the subset of `todos` whose question answered `absent` in all of them: the same objects, in chain order. Code that only reads a chain result needs no change. Code that builds one of these two interfaces itself must now supply `absentTodos` (an empty array when nothing is proven absent). + - **Only a positive proof names an entry.** These cases answer `unknown` and leave it off `absentTodos`: + - a value the question cannot read (a function, a promise, a scalar, a getter that throws); + - a stack that is not a plain object; + - any `plugins`, `devPlugins` or `tiers` entry, since a plugin, or the platform plugins a tier preset loads, can register metadata the stack does not show. + + An entry that judges a conversion which applied an edit in the same run is never named either. + - **The first batch is 25 entries** (5 from protocol 17, 20 from protocol 18). Each one's surface lives only under named top-level stack keys: `analyticsCubes`, `apis`, `jobs`, `mappings`, `hooks`, `agents`, `tools`, `dashboards` (with `reports` and `pages` for the chart-config entry), `datasets`, `permissions` and `sharingRules`. None of them names a code door. Each entry was also checked to confirm that its acceptance criteria send the author to no stored row and no runtime door. Every other entry is never named absent, so it is listed exactly as before. + - **`os migrate meta`** lists `todos` minus `absentTodos`. After the listed notices it prints one line that counts the entries proven absent and names `--all`. A second line says that the proof covers the stack this run loaded, and not metadata a deployment stores. + - `--all` prints each of those entries in full, with the keys it was proven absent under. + - `--json` keeps `todos` whole and adds `absentTodos`, plus `hops[].absentTodos` with `--step`. + - `--step` reports each hop's listed count, and adds a `not listed` count to the hop line when that count is not zero. + - A run whose only notices are proven absent still writes `--out`. +- dd39171: The path an author writes as a public form's `sharing.publicLink` now answers: `GET /forms/SLUG` redirects to the Console's public form page, `/_console/f/SLUG`, when the anonymous form door serves that slug + + Clause-②: yes (widening) + + - **What was wrong.** An app declares a public form as `sharing: { enabled: true, allowAnonymous: true, publicLink: '/forms/contact-us' }`, the spelling the platform's own examples use. The Console serves that form to an anonymous visitor at `/_console/f/contact-us`, but the path as written answered `404 ENDPOINT_NOT_FOUND`, so the link an author put on a website reached nothing. + - **What it does now.** Wherever the Console is mounted (`os serve` / `os dev` with the Console, or any host that mounts `createConsoleStaticPlugin` from `@objectstack/cli/console`), `GET /forms/SLUG` answers `302` with `Location: /_console/f/SLUG`, followed by the request's query string. It does so only when the anonymous form door, `GET /api/v1/forms/SLUG`, serves the form to the same request: the form's `sharing` has `enabled` and `allowAnonymous` set and names the slug, no other metadata layer withdraws it, and the deployment's tenancy posture lets it take an anonymous submission. The redirect asks that door in-process with the visitor's own request, so it decides nothing the door does not, and it reveals nothing the door does not. + - **Unchanged.** Every other request under `/forms/` answers exactly as before: a disabled form, a form not open to anonymous visitors, an unknown slug, a form the posture withholds, any method other than `GET` / `HEAD`, a trailing slash or a deeper path all get the same `404 ENDPOINT_NOT_FOUND`. Nothing is mounted at the root for a bare slug. The signed-in Console route `/_console/forms/NAME` is untouched. A deployment without the Console (`--no-ui`, `--no-console`, `OS_DISABLE_CONSOLE=1`, or no built Console) mounts no redirect. + - **Status and target.** The redirect is a `302`, never a permanent one, because whether a form is served can change with its sharing. The `Location` path is built from the Console path and the slug, encoded as one path segment, so no request can point it anywhere else. The request's query string is carried verbatim after that path, because the public form page seeds its fields from `?prefill_FIELD=` parameters: `/forms/contact-us?prefill_source=website` lands on `/_console/f/contact-us?prefill_source=website`. A query string can change only the query of the page the visitor lands on, never its origin or path. + - **Nothing to migrate.** No key, export or signature changes. `sharing.publicLink` keeps its meaning and its accepted spellings (`/forms/x`, `forms/x` and `x` name one slug). +- a959493: `os migrate meta --write` writes the chain's mechanical changes into the authored source files, in place, at every site it can prove + + Clause-②: yes (widening) + + - A new flag on the authored-source mode: `os migrate meta --from N --write`. Without it nothing changes: the dry run, its report and its `--json` payload are what they were, and `--out` still writes its snapshot. + - What it writes: each mechanical change the chain applied (`applied`), at a site it traces to one object or array literal in one project file — through `define*` calls and the `.create(…)` factories `@objectstack/spec` exports, module-level `const` bindings, relative imports and re-exports, and `Object.values()` over a namespace import — when the loaded value matches that literal and nothing else references the bindings on the way. Only that site's bytes change: a renamed key keeps its value and its comments, a removed key takes its own line(s), and every other byte (comments, formatting, key order) stays as it was. + - What it refuses, each change listed with the reason (`--json`: `write.manual[].kind`): `computed`, `helper`, `spread`, `shared`, `outside-project`, `mismatch`, `injected`, `unspellable`, `layout` and `unattributed`; and `entangled`, because a conversion's edits are written whole or not at all. + - What it never writes: the semantic changes (`todos`), which stay listed exactly as before, and a site a conversion declines, for which no mechanical change exists. + - After writing it re-runs the chain over the written sources. Unless the re-run applies exactly the changes it left, it restores every file it wrote and exits 1. + - `--json` gains a `write` key, only with `--write`: `status`, `files`, `written`, `manual`, `unexplained` and `verification`. + - `--write` is exclusive with `--stored`; `--stored --apply` is unchanged. + +### Patch Changes + +- bafb58b: `os serve` (and `objectstack start`, which runs it) now says at boot when an app's branding logo or favicon will not be served. Before, the runtime assets route was skipped without a word when its directory was absent, so an artifact booted outside its project directory drew a broken logo and favicon and nothing in the boot output said why. + + Clause-②: no + + - Once the boot settles, every loaded app whose `branding.logo` or `branding.favicon` is a root path under `/runtime/assets/` that the route will not serve gets one warning line per file. The line names the apps and keys that use the file, the directory searched, and whether that directory came from `OS_RUNTIME_ASSETS_DIR` or the `assets/` default under the working directory. When the directory does not exist, the line says so and says nothing under `/runtime/assets/` is mounted for this run. + - The apps read are the ones the console is served, through the same metadata protocol read that `GET /api/v1/meta/app` answers from. Config boots and artifact boots are both covered. + - Whether a file is servable is decided by the route's own filename resolution, so the warning and the route cannot disagree. Absolute URLs, protocol-relative URLs, data URIs, relative paths and other root paths are not checked. + - The line goes through the kernel logger at `warn`. It shows in the banner's *Boot diagnostics* block, streams live at `--log-level debug` or `info`, and is hidden at `error` or `silent` like every other boot warning. + - ⛔ What `/runtime/assets/*` serves does not change. No route is added or removed, and the artifact still carries no asset files: ship the `assets/` directory beside it, or point `OS_RUNTIME_ASSETS_DIR` at the files. +- 4935c66: The startup banner prints one line per warning class, each warning appears once, and a localhost boot no longer warns that OAuth is unencrypted. + + Clause-②: no + + - **One line per class.** Flows that declare a trigger but are not bound are grouped by trigger type and reason, with the flows listed: `⚠ 8 flows declare a 'schedule' trigger but are NOT bound — disabled by deployment policy — … (OS_AUTOMATION_SCHEDULED_WORK_ENABLED is unset or not truthy), so no time trigger arms …: flow_a, flow_b, …`. Before, the full reason (up to ~650 characters) printed once per flow. The banner now shows the reason's first sentence; `--log-level debug` still streams each flow's full reason. A real binding failure, or a missing trigger, keeps its own line, worded as before. + - **Printed once.** *Boot diagnostics* no longer repeats the automation plugin's per-flow `… is NOT bound` and shadowed-flow warnings, which the banner's `Flows:` list already shows. Its header counts them instead: `⚠ Boot diagnostics — 5 warnings logged during startup (8 more already listed above):`. Every other boot warning replays exactly as before. A boot that fails before the banner still replays all of them. + - **OAuth on loopback.** `OAuth is served UNENCRYPTED: …` is logged at `info` when the issuer's host is loopback (`localhost`, `*.localhost`, `127.0.0.0/8`, `::1`), so it is not shown at the default `warn` level. It stays `warn` on a private or link-local issuer. The sentence and the transport rule are unchanged. + - ⛔ Nothing you author changes. Which flows bind, the scheduled-work switch, the transport rule, the service-automation warning an embedded host reads, and every public key, export and parameter are unchanged. +- 54ace18: fix(cli): `os migrate meta --out FILE` writes its snapshot on a range that crosses no step (#22116) + + Clause-②: no + + `os migrate meta --from 18 --to 18 --out FILE` exited 0, printed no snapshot line and wrote no `FILE`. The same command with `--json` wrote `FILE`. The human report returned early on a run with nothing to migrate, and that return came before the `--out` write. An operator or a CI step that keeps `FILE` as the record of the run then found no file, or read an earlier run's file as this one's. + + - The human mode now writes `FILE` and prints the line that names it on every run. On a run with nothing to migrate, the line comes after the range answer, before `--write`'s outcome and the data migrations, the same order as on every other run. + - The bytes are the ones `--json` writes for the same run: the stack the chain returned, which for a range with no step is the stack as loaded. + - The fix sits in the branch both "nothing to migrate" answers share. A range with steps that applies and lists nothing takes the same branch, but no range reaches it on this build, because every major carries semantic notices and the chain lists them all. + - Unchanged: the `--json` mode, `--write`, `--stored` and the chain. A range with steps writes `FILE` exactly as before. +- Updated dependencies [c28f317] +- Updated dependencies [aa71c4d] +- Updated dependencies [1920cf3] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [a7a48b7] +- Updated dependencies [b88c356] +- Updated dependencies [1abfc58] +- Updated dependencies [8a399b2] +- Updated dependencies [db87a02] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [6befe19] +- Updated dependencies [8caa131] +- Updated dependencies [f0022c4] +- Updated dependencies [1fb274e] +- Updated dependencies [1fb274e] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [f85a83b] +- Updated dependencies [2015c54] +- Updated dependencies [0db5ad5] +- Updated dependencies [8601526] +- Updated dependencies [f2a45db] +- Updated dependencies [3d91885] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [ae97841] +- Updated dependencies [56bf27a] +- Updated dependencies [e67ba80] +- Updated dependencies [cdeabec] +- Updated dependencies [4935c66] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/plugin-security@17.8.0 + - @objectstack/plugin-auth@17.8.0 + - @objectstack/verify@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/service-automation@17.8.0 + - @objectstack/service-realtime@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/service-storage@17.8.0 + - @objectstack/service-messaging@17.8.0 + - @objectstack/service-settings@17.8.0 + - @objectstack/metadata-protocol@17.8.0 + - @objectstack/service-datasource@17.8.0 + - @objectstack/plugin-sharing@17.8.0 + - @objectstack/plugin-approvals@17.8.0 + - @objectstack/rest@17.8.0 + - @objectstack/runtime@17.8.0 + - @objectstack/service-analytics@17.8.0 + - @objectstack/lint@17.8.0 + - @objectstack/cloud-connection@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/plugin-hono-server@17.8.0 + - @objectstack/plugin-email@17.8.0 + - @objectstack/console@17.8.0 + - @objectstack/client@17.8.0 + - @objectstack/driver-memory@17.8.0 + - @objectstack/driver-mongodb@17.8.0 + - @objectstack/driver-sql@17.8.0 + - @objectstack/driver-sqlite-wasm@17.8.0 + - @objectstack/driver-turso@17.8.0 + - @objectstack/mcp@17.8.0 + - @objectstack/metadata@17.8.0 + - @objectstack/plugin-audit@17.8.0 + - @objectstack/plugin-pinyin-search@17.8.0 + - @objectstack/plugin-webhooks@17.8.0 + - @objectstack/service-cache@17.8.0 + - @objectstack/service-job@17.8.0 + - @objectstack/service-package@17.8.0 + - @objectstack/service-queue@17.8.0 + - @objectstack/service-sms@17.8.0 + - @objectstack/trigger-api@17.8.0 + - @objectstack/trigger-record-change@17.8.0 + - @objectstack/trigger-schedule@17.8.0 + - @objectstack/account@17.8.0 + - @objectstack/setup@17.8.0 + - create-objectstack@17.8.0 + - @objectstack/formula@17.8.0 + - @objectstack/observability@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index 6c3190acd5a..7beff374554 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/cli", - "version": "17.7.0", + "version": "17.8.0", "description": "Command Line Interface for ObjectStack Protocol", "main": "dist/index.js", "types": "dist/index.d.ts", diff --git a/packages/client-react/CHANGELOG.md b/packages/client-react/CHANGELOG.md index 9645096ff13..9860e8a0bdb 100644 --- a/packages/client-react/CHANGELOG.md +++ b/packages/client-react/CHANGELOG.md @@ -1,5 +1,31 @@ # @objectstack/client-react +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/client@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/client-react/package.json b/packages/client-react/package.json index 1057c8b4adf..e0432e98e5d 100644 --- a/packages/client-react/package.json +++ b/packages/client-react/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/client-react", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "React hooks for ObjectStack Client SDK", "main": "dist/index.js", diff --git a/packages/client/CHANGELOG.md b/packages/client/CHANGELOG.md index bc5a0fd5a13..2d415546554 100644 --- a/packages/client/CHANGELOG.md +++ b/packages/client/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/client +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/client/package.json b/packages/client/package.json index 73c617326c2..6a0f63abae3 100644 --- a/packages/client/package.json +++ b/packages/client/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/client", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Official Client SDK for ObjectStack Protocol", "main": "dist/index.js", diff --git a/packages/cloud-connection/CHANGELOG.md b/packages/cloud-connection/CHANGELOG.md index dc0f5166ecb..e87eac10194 100644 --- a/packages/cloud-connection/CHANGELOG.md +++ b/packages/cloud-connection/CHANGELOG.md @@ -1,5 +1,56 @@ # @objectstack/cloud-connection +## 17.8.0 + +### Patch Changes + +- 8601526: `RuntimeConfigPluginConfig.controlPlaneUrl`'s published docblock no longer says that an empty string declares "this runtime IS the cloud". It now says what the constructor does: `''` keeps marketplace and install requests on this origin, and that is all it says. + + Clause-②: no + + - The runtime that passes `''` may serve the catalog itself or proxy a control plane it does not name. The CLI's cloud-connected `os serve` passes `''` while its marketplace proxy forwards to the control plane `resolveCloudUrl()` answers. So `''` reads neither as "this runtime is the cloud" nor as "there is no upstream". This matches the `AppShellRuntimeConfig.cloudUrl` doc in `@object-ui/app-shell`. + - A runtime with no control plane says so with a decline spelling (`'off'` / `'none'` / `'local'` / `'disabled'`), in `controlPlaneUrl` or in `OS_CLOUD_URL`. The docblock now says this too. + - ⛔ No code, type, export or default change. The served `cloudUrl` and the telemetry posture do not change. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [b88c356] +- Updated dependencies [1abfc58] +- Updated dependencies [db87a02] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [6befe19] +- Updated dependencies [8caa131] +- Updated dependencies [1fb274e] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [2015c54] +- Updated dependencies [f2a45db] +- Updated dependencies [3d91885] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [ae97841] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-protocol@17.8.0 + - @objectstack/runtime@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/cloud-connection/package.json b/packages/cloud-connection/package.json index f2757402ae2..8f249c619b8 100644 --- a/packages/cloud-connection/package.json +++ b/packages/cloud-connection/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/cloud-connection", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Runtime-side client for an ObjectStack cloud control plane — marketplace browse proxy, install-local, device-code binding, org catalog and installed views, and the /api/v1/runtime/config discovery endpoint. Open mechanism (cloud ADR-0008): the hub service, plan policy, and entitlements stay server-side.", "type": "module", diff --git a/packages/connectors/connector-mcp/CHANGELOG.md b/packages/connectors/connector-mcp/CHANGELOG.md index c9ff05c2a69..62f3587aec9 100644 --- a/packages/connectors/connector-mcp/CHANGELOG.md +++ b/packages/connectors/connector-mcp/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/connector-mcp +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/connectors/connector-mcp/package.json b/packages/connectors/connector-mcp/package.json index 02a8cc55511..e3ffa4bf407 100644 --- a/packages/connectors/connector-mcp/package.json +++ b/packages/connectors/connector-mcp/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-mcp", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Model Context Protocol (MCP) connector for ObjectStack — a generic adapter that turns any MCP server's tools into a connector's actions on the automation engine's connector registry (ADR-0024).", "main": "dist/index.js", diff --git a/packages/connectors/connector-openapi/CHANGELOG.md b/packages/connectors/connector-openapi/CHANGELOG.md index bba801f52b6..ea5e3bf2bc1 100644 --- a/packages/connectors/connector-openapi/CHANGELOG.md +++ b/packages/connectors/connector-openapi/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/connector-openapi +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/connectors/connector-openapi/package.json b/packages/connectors/connector-openapi/package.json index 60057fd40e8..c4965b11691 100644 --- a/packages/connectors/connector-openapi/package.json +++ b/packages/connectors/connector-openapi/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-openapi", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "OpenAPI 3.x connector generator for ObjectStack — turns a declarative OpenAPI document into connector actions on the automation engine's registry, with a self-contained static-auth HTTP transport (ADR-0023).", "main": "dist/index.js", diff --git a/packages/connectors/connector-rest/CHANGELOG.md b/packages/connectors/connector-rest/CHANGELOG.md index 67f42565966..d8343e5e959 100644 --- a/packages/connectors/connector-rest/CHANGELOG.md +++ b/packages/connectors/connector-rest/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/connector-rest +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/connectors/connector-rest/package.json b/packages/connectors/connector-rest/package.json index 32cfaf55ffb..31707235c13 100644 --- a/packages/connectors/connector-rest/package.json +++ b/packages/connectors/connector-rest/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-rest", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Generic REST connector for ObjectStack — the reference concrete connector that registers a `request` action on the automation engine's connector registry (ADR-0018 §Addendum).", "main": "dist/index.js", diff --git a/packages/connectors/connector-slack/CHANGELOG.md b/packages/connectors/connector-slack/CHANGELOG.md index b2ab15e5b46..1387d9bbcba 100644 --- a/packages/connectors/connector-slack/CHANGELOG.md +++ b/packages/connectors/connector-slack/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/connector-slack +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/connectors/connector-slack/package.json b/packages/connectors/connector-slack/package.json index d2f961f2d60..76a30fa4399 100644 --- a/packages/connectors/connector-slack/package.json +++ b/packages/connectors/connector-slack/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/connector-slack", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Slack Web API connector for ObjectStack — registers `chat.postMessage` / `chat.update` / `call` actions on the automation engine's connector registry (ADR-0018 §Addendum, ADR-0022).", "main": "dist/index.js", diff --git a/packages/console/CHANGELOG.md b/packages/console/CHANGELOG.md index 03d0c0b3fd9..db9e1c4f263 100644 --- a/packages/console/CHANGELOG.md +++ b/packages/console/CHANGELOG.md @@ -1,5 +1,88 @@ # @objectstack/console +## 17.8.0 + +### Minor Changes + +- 299a2c6: Console (objectui) refreshed to `a58626c88dc8`. Frontend changes in this range: + + Derived from the changesets objectui declared over the range — 36 releasing of 36 changesets added across 36 non-merge commits. + + - **minor** — The timeline's unused "Overdue" bucket label is removed: `timeline.bucket.overdue` is gone from all ten language packs and from the timeline's built-in English defaults (objectui#… (objectui `c910630cf`) + - **minor** — **BREAKING** — Inside a dashboard widget's legacy `component` envelope, a `metric-card` is now judged by the slot's component arm alone, so every key that arm refuses is refused there too, with… (objectui `89cc738da`) + - **minor** — **BREAKING** — "Recently Accessed" shows each item's own label, in the current language, and a visit that does not change the list no longer writes the `ui.recent` preference (objectui#11678). (objectui `48c82c9d5`) + - **minor** — The calendar and the timeline start the week on the first day of the week of the user's locale (objectui#11675), instead of a fixed Sunday (calendar) and a fixed Monday (timeline)… (objectui `9ca3cacbe`) + - **minor** — The console's settings pages resolve icons through the shared `getLazyIcon` helper and no longer log `[lucide-react]: Name in Lucide DynamicIcon not found` (objectui#11679). (objectui `abd374bb2`) + - **minor** — `DefaultLoginPage` and `DefaultRegisterPage` offer a generic sign-up only where the server would accept one (objectui#11705). Under the default `invite_only` audience posture the… (objectui `7e2d5b0ee`) + - **minor** — Objects with a picklist-bound field save again from the OWD overview, the Setup fields and objects pages, the metadata-admin embedded-item editor, and `MetadataService.saveFields`… (objectui `da3545375`) + - **minor** — The keyboard-shortcuts dialog (`?`) lists only shortcuts that do something (objectui#11674). (objectui `de96f3d0c`) + - **minor** — **BREAKING** — A `metric-card` placed in a dashboard's widget slot refuses `label` by name and points at `title`, the key its heading is drawn from (objectui#4425). (objectui `eb4552e71`) + - **minor** — The console's login and register pages offer a generic sign-up only where the server would accept one (objectui#11691). `/api/v1/auth/config` states the sign-up rule as two keys,… (objectui `daa7caff4`) + - **minor** — An object-bound timeline no longer heads every past date "Overdue": a day before today goes under a neutral "Earlier" bucket, translated in every language (objectui#11676). (objectui `ce464d958`) + - **minor** — Studio reads shared picklists: a select field can use one, an object with a picklist-bound field saves again, and the picklist page is read-only (objectui#10202, the objectui half… (objectui `c3623eb11`) + - **minor** — The AI chat's tool-approval card and its "Open in Builder" handoff card are translated in every language (objectui#11667). (objectui `57d82cb34`) + - **minor** — The chat launchers show a marker while a proposed plan awaits the user's approval (objectui#11666, item 6 of objectui#2458). A user who closed the chat with a blueprint still wait… (objectui `848ba0e12`) + - **minor** — Five fixes on the AI build surface, from the 2026-10-05 cloud acceptance run (objectui#11658). (objectui `fc3c2cc1c`) + - **minor** — `action:group` and `action:menu` no longer read a member's `properties.params` (objectui#11638). A container member's `properties.params` no longer reaches the action runner. (objectui `73b5d7764`) + - **minor** — `LoginForm` shows its "Don't have an account? Sign up" row only when the caller passes `registerUrl` (objectui#11634). The prop used to default to `'/register'`, so a caller that… (objectui `f1a177c41`) + - **minor** — A field group's `visibleWhen` now gates its section on the record detail page, the same way it gates the section on the entry form (objectui#11630). Take an object whose `fieldGro… (objectui `76993f8ef`) + - **patch** — fix(plugin-tree): tree cells draw the same field faces as list cells, so a boolean no longer shows as `true` (objectui#11686) (objectui `a58626c88`) + - **patch** — **The record header's highlight chips share the row's free width and truncate only when it runs out (objectui#11684).** A Product with SKU "QA Widget 1" read "QA Wid…" in the reco… (objectui `7300fcafe`) + - **patch** — An authored `view:calendar` or `view:timeline` node loads its plugin and renders the calendar or the timeline, and a console boot no longer logs the registry's race warning for th… (objectui `e6dcd85cc`) + - **patch** — The approvals inbox's request drawer shows its record summary the way the record page shows the record (objectui#11677). (objectui `3409fe89e`) + - **patch** — The flow designer saves a screen field's `Min` and `Max` as numbers (objectui#11664). (objectui `5ba255538`) + - **patch** — The metric cards no longer write authored keys they do not read onto the page (objectui#4425). Rendered through `SchemaRenderer`, which is how every dashboard draws its KPI tiles… (objectui `a600924f2`) + - **patch** — Console, record page and Studio copy from the 2026-10-05 cloud acceptance run (objectui#11659). (objectui `f9f4a62d5`) + - **patch** — The flow designer's SLA escalation switch on an approval node no longer writes a refused `escalation: { enabled: false }` block, and switching it off keeps the values the author e… (objectui `9f3ed7bca`) + - **patch** — Cancelling a background import shows the rows the server already committed, with Undo (objectui#11650). `ImportWizard`'s Cancel used to show "Import cancelled · 0 imported" withou… (objectui `846f98251`) + - **patch** — Drag-to-reorder works on a grouped sidebar menu, within each level (objectui#11626). `NavigationRenderer` had a sortable path only in its group-free arm. Every stock app's menu is… (objectui `b88937b65`) + - **patch** — The `object-kanban` board totals the view's `summarizeField` in each column header (objectui#11629). (objectui `f4370f426`) + - **patch** — On an object that declares no list view, a grid toolbar change no longer sends a save the door refuses (objectui#11643). Such an object opens on the "All Records" tab the console… (objectui `43671468d`) + - **patch** — The console's `/verify-email?token=…` page verifies the address again (objectui#11633). It used to send the token as `POST /api/v1/auth/verify-email` with a JSON body. better-auth… (objectui `8057a8b14`) + - **patch** — On a runtime with no marketplace that still mounts install-local (an offline boot, `OS_CLOUD_URL=off`), a local install's Details page now offers its local menu: re-seed sample da… (objectui `0baf86f1b`) + - **patch** — A dataset-bound dashboard widget names its comparison window from `compareTo.kind` (objectui#11632). `previousPeriod` reads "vs previous period" and `previousYear` reads "vs last… (objectui `e398a54f0`) + - **patch** — Two grid toolbar changes to one view in one session now both survive a reload (objectui#11642). Before, the second change overwrote the first: changing density and then sorting by… (objectui `531b26c8f`) + - **patch** — A lookup whose `dependsOn` names a parent field drops its selection when that parent changes or is cleared (objectui#11631). (objectui `c00039842`) + - **patch** — A grid toolbar change on a served view is stored where the save door keeps it, so it survives a reload (objectui#11625). This covers density, a column-header sort and the hide-fie… (objectui `59917c4b2`) + + ⚠️ 3 of these carry a breaking change: 3 by the author's own breaking annotation in the changeset body — objectui declares no `major` inside a launch window (`scripts/check-changeset-no-major.mjs`). Each is marked **BREAKING** in the list above — read them before compiling the release record. + + + + objectui range: `0abd4f9f8769...a58626c88dc8` + ## 17.7.0 ### Minor Changes diff --git a/packages/console/package.json b/packages/console/package.json index 53220c69b79..8393baf2fb8 100644 --- a/packages/console/package.json +++ b/packages/console/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/console", - "version": "17.7.0", + "version": "17.8.0", "description": "Prebuilt Console SPA pinned to this framework release, installed as a dependency of @objectstack/cli. Source of truth: @object-ui/console (https://github.com/objectstack-ai/objectui).", "license": "Apache-2.0", "homepage": "https://github.com/objectstack-ai/objectstack/tree/main/packages/console", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index e828dab7928..33d632e93a6 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,43 @@ # @objectstack/core +## 17.8.0 + +### Minor Changes + +- c28f317: feat(core): one by-name read of the security catalog (`createSecurityCatalogReader`) + + Clause-②: yes (widening) + + - **What is new.** `createSecurityCatalogReader({ registry, metadata })` returns a reader with two members: `resolve(type, name)`, the definition a position, permission set or capability name resolves to (or `undefined`), and `list(type)`, one entry per name. `type` is `'position' | 'permission' | 'capability'`. Each entry is `{ type, name, definition, source, packageId? }`. The types `SecurityCatalogType`, `SecurityCatalogSourceName`, `SecurityCatalogRegistry`, `SecurityCatalogMetadataService`, `SecurityCatalogSources`, `SecurityCatalogEntry` and `SecurityCatalogReader` are exported with it. + - **Where it reads.** ObjectQL's `SchemaRegistry` (`engine.registry`) first, then the kernel `metadata` service for the names the registry does not hold. Neither holds the whole catalog: the engine registry carries the platform's own permission sets and every package manifest's catalog items but no stack-declared position, and the metadata service carries the stack-declared positions but not the platform's permission sets. Both are required; construction refuses a missing one. + - **A name two packages ship** resolves the way the registry's by-name read does today: a stored override first, else the first-registered package's body. + - **What it does not answer.** Whether an item is in effect: the row `active` flag stays the authority, and no definition carries it. The position → permission-set binding. Organization scope: the catalog is environment-level. + - **Failures are loud.** A reader that throws, or a metadata read that lost a loader and found nothing, raises `AuthzStoreUnavailableError` (`SERVICE_UNAVAILABLE`, 503) instead of answering "no such item". A definition owned by a disabled package answers neither member. + - Nothing calls the reader yet; no grant changes. + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/core/package.json b/packages/core/package.json index 9ed424c7930..f3d1e681bd0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/core", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Microkernel Core for ObjectStack", "type": "module", diff --git a/packages/create-objectstack/CHANGELOG.md b/packages/create-objectstack/CHANGELOG.md index a28c884a093..47a7bfee889 100644 --- a/packages/create-objectstack/CHANGELOG.md +++ b/packages/create-objectstack/CHANGELOG.md @@ -1,5 +1,9 @@ # create-objectstack +## 17.8.0 + +No changes in this release. + ## 17.7.0 ### Minor Changes diff --git a/packages/create-objectstack/package.json b/packages/create-objectstack/package.json index 30284d7034f..a0f5e43c3b4 100644 --- a/packages/create-objectstack/package.json +++ b/packages/create-objectstack/package.json @@ -1,6 +1,6 @@ { "name": "create-objectstack", - "version": "17.7.0", + "version": "17.8.0", "description": "Create a new ObjectStack project — npx create-objectstack", "bin": { "create-objectstack": "./bin/create-objectstack.js" diff --git a/packages/drivers/driver-memory/CHANGELOG.md b/packages/drivers/driver-memory/CHANGELOG.md index 426064182fd..130f9abc1e2 100644 --- a/packages/drivers/driver-memory/CHANGELOG.md +++ b/packages/drivers/driver-memory/CHANGELOG.md @@ -1,5 +1,32 @@ # @objectstack/driver-memory +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/drivers/driver-memory/package.json b/packages/drivers/driver-memory/package.json index f12fb1f6c9f..8e6ae1bcfda 100644 --- a/packages/drivers/driver-memory/package.json +++ b/packages/drivers/driver-memory/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-memory", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "In-Memory Driver for ObjectStack (Reference Implementation)", "main": "dist/index.js", diff --git a/packages/drivers/driver-mongodb/CHANGELOG.md b/packages/drivers/driver-mongodb/CHANGELOG.md index c32cedc3d25..5b7737a4401 100644 --- a/packages/drivers/driver-mongodb/CHANGELOG.md +++ b/packages/drivers/driver-mongodb/CHANGELOG.md @@ -1,5 +1,32 @@ # @objectstack/driver-mongodb +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/drivers/driver-mongodb/package.json b/packages/drivers/driver-mongodb/package.json index 6ab6b4b9ae9..c6b4ad4ebee 100644 --- a/packages/drivers/driver-mongodb/package.json +++ b/packages/drivers/driver-mongodb/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-mongodb", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "MongoDB Driver for ObjectStack - Native document database driver via official mongodb client", "main": "dist/index.js", diff --git a/packages/drivers/driver-sql/CHANGELOG.md b/packages/drivers/driver-sql/CHANGELOG.md index e97bf8b5b47..32117c2a5b2 100644 --- a/packages/drivers/driver-sql/CHANGELOG.md +++ b/packages/drivers/driver-sql/CHANGELOG.md @@ -1,5 +1,33 @@ # @objectstack/driver-sql +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/observability@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/drivers/driver-sql/package.json b/packages/drivers/driver-sql/package.json index dfb89f0b533..3628f69d1ae 100644 --- a/packages/drivers/driver-sql/package.json +++ b/packages/drivers/driver-sql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-sql", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "SQL Driver for ObjectStack - Supports PostgreSQL, MySQL, SQLite via Knex", "main": "dist/index.js", diff --git a/packages/drivers/driver-sqlite-wasm/CHANGELOG.md b/packages/drivers/driver-sqlite-wasm/CHANGELOG.md index b84df8f6e3d..9f91c6276b2 100644 --- a/packages/drivers/driver-sqlite-wasm/CHANGELOG.md +++ b/packages/drivers/driver-sqlite-wasm/CHANGELOG.md @@ -1,5 +1,31 @@ # @objectstack/driver-sqlite-wasm +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/driver-sql@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/drivers/driver-sqlite-wasm/package.json b/packages/drivers/driver-sqlite-wasm/package.json index bdcf524b2cb..4c99342aa62 100644 --- a/packages/drivers/driver-sqlite-wasm/package.json +++ b/packages/drivers/driver-sqlite-wasm/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-sqlite-wasm", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "WASM SQLite Driver for ObjectStack — runs in browser/WebContainer (StackBlitz) without native bindings", "keywords": [ diff --git a/packages/drivers/driver-turso/CHANGELOG.md b/packages/drivers/driver-turso/CHANGELOG.md index 842e60a9515..4c2f3c8c3aa 100644 --- a/packages/drivers/driver-turso/CHANGELOG.md +++ b/packages/drivers/driver-turso/CHANGELOG.md @@ -1,5 +1,31 @@ # @objectstack/driver-turso +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/driver-sql@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/drivers/driver-turso/package.json b/packages/drivers/driver-turso/package.json index e042378aefd..8e4caaba784 100644 --- a/packages/drivers/driver-turso/package.json +++ b/packages/drivers/driver-turso/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/driver-turso", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Turso/libSQL Driver for ObjectStack — Edge-first SQLite with embedded replicas", "keywords": [ diff --git a/packages/formula/CHANGELOG.md b/packages/formula/CHANGELOG.md index a228a7206d5..dd39f33df4e 100644 --- a/packages/formula/CHANGELOG.md +++ b/packages/formula/CHANGELOG.md @@ -1,5 +1,28 @@ # @objectstack/formula +## 17.8.0 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/formula/package.json b/packages/formula/package.json index 3902ee4c076..12e8b8332d9 100644 --- a/packages/formula/package.json +++ b/packages/formula/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/formula", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "ObjectStack canonical expression engine — CEL (cel-js) + ObjectStack stdlib + dialect registry", "main": "dist/index.js", diff --git a/packages/lint/CHANGELOG.md b/packages/lint/CHANGELOG.md index 382ada55624..7b473e2f1ed 100644 --- a/packages/lint/CHANGELOG.md +++ b/packages/lint/CHANGELOG.md @@ -1,5 +1,135 @@ # @objectstack/lint +## 17.8.0 + +### Minor Changes + +- f85a83b: fix(lint)!: the object save door refuses a formula field whose expression `os build` refuses (#22019) + + Clause-②: no (narrowing) + + `formulas.mdx` says the same `validateExpression` validator backs `os build` and metadata registration. At the object save door it did not. A formula field calling an unregistered function, such as `sqrt(record.amount)`, was refused by `os build` as an unknown function, but `PUT /api/v1/meta/object/:name` answered 200, stored it, and the field read `null` on every row. + + The runtime publish gate now runs the build's own formula check on an object write. The registry entry for the build's expression rule (`validateStackExpressions`) declared the flow, action and hook writes and never the object write, so the gate never dispatched it there. It now declares `object` as well, for one of its passes: a formula field's `expression`. The door's verdict is the build's finding: the same rule id (`expression-invalid`), location (`object 'NAME' · field 'FIELD' expression`), message and hint. + + **BREAKING — what moves for consumers.** + + - An object write in publish mode answered 200 for a formula field whose expression the shared validator refuses. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that field's `expression`. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`). + - The verdict is the one `os build`, `os validate` and `os lint` already gave: an unknown function, a field the object does not declare, a bare field reference (`amount` instead of `record.amount`), and the other errors in the build's formula check. Its warnings now ride the save response as advisories, as they already did for a flow write. + + **Remedy.** Fix the expression: the message names the unknown function or field and the position, as `os build` already requires. Use one of the functions `introspectScope` lists, qualify field reads as `record.FIELD`, or compute the value in a stored field and reference it. Saving it as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged. + + **Unchanged.** + + - Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps reading, with the formula still `null`, until it is next saved. At that save the gate judges it, because the differential compares the write against the stored universe without its own stored row. + - The other expressions an object carries are still not judged at this door: validation-rule predicates, the field-rule slots (`requiredWhen`, `readonlyWhen`, `conditionalRequired`, `visibleWhen`), option `visibleWhen`, and the object's own action predicates. `os build` judges them, and the door does not, as before. Each needs its own crossing, measured over the stored corpus first. + - `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write. + - Measured before crossing: every formula field this repository ships has 0 refusals and 0 advisories at the door. That is 29 fields on 28 objects: examples 7 on 6, and the platform `display_title` formulas 22 on 22. + - No public export or signature moves. `validateStackExpressions(stack)` keeps its signature. The registry entry reaches the passes through an internal function that is not on the package's entry. The built entry declarations differ only in one doc comment, on `AuthoringRuleContext.runtimeWriteType`. + + +- f2a45db: fix(lint)!: the object save door refuses a field-rule slot whose predicate `os build` refuses (#22032) + + Clause-②: no (narrowing) + + `formulas.mdx` says the same `validateExpression` validator backs `os build` and metadata registration. For a field's rule slots it did not, at the object save door. A field whose `requiredWhen` read a bare field, such as `amount > 1`, or whose `visibleWhen` called an unregistered function, such as `sqrt(record.amount) > 1`, was refused by `os build` at error, but `PUT /api/v1/meta/object/:name` answered 200 and stored it. + + The runtime publish gate now runs the build's field-rule-slot check on an object write. The build's expression rule (`validateStackExpressions`) was already on the object door for formula fields and validation-rule predicates. On an object write it now also judges each field's `requiredWhen`, `readonlyWhen` and `visibleWhen` the way the build does, with the build's three gates on them: the `parent` gate, the null-guard check over `requiredWhen`, and the refusal of a `requiredWhen` or `readonlyWhen` that reads through a reference field. The door's verdict is the build's finding: the same rule id (`expression-invalid`), location (`object 'NAME' · field 'FIELD' SLOT`), message and hint. + + **BREAKING — what moves for consumers.** + + - An object write in publish mode answered 200 for a field whose `requiredWhen`, `readonlyWhen` or `visibleWhen` the shared validator refuses. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that slot. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`). + - The verdict is the one `os build`, `os validate` and `os lint` already gave: an unknown function, a field the object does not declare, a bare field reference (`amount` instead of `record.amount`), a syntax error, a root a field-level rule never binds (such as `current_user`), a `parent` read on an object that does not declare exactly one `master_detail` relationship, an ordering or arithmetic operator in `requiredWhen` applied to a nullable field with no `!= null` guard, and a `requiredWhen` or `readonlyWhen` that reads through a reference field (`record.account.tier`, or `parent.REF.FIELD`). Its warnings now ride the save response as advisories. + - A detail object's `requiredWhen` or `readonlyWhen` that reads through one of its master's reference fields (`parent.REF.FIELD`) is judged whenever the master is in the write's context, and that includes a save of the master itself. So a master save can answer 422 with an issue located at a stored detail's field. Fix the detail's predicate, then save the master again. + + **Remedy.** Fix the predicate: the message names the unknown function or field, the unbound root, the unguarded operand or the reference read, and the position, as `os build` already requires. Qualify field reads as `record.FIELD`, use one of the functions `introspectScope` lists, guard a nullable operand in `requiredWhen` with `record.FIELD != null && …`, and move a check that must read through `record.REF` into a `validations[]` `script` rule, whose `condition` is read one hop through a reference; a read through `parent.REF` has no such surface, so read a column the master declares instead (denormalise the value onto it). Saving it as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged. + + **Unchanged.** + + - Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps loading until it is next saved. At that save the gate judges it, because the differential compares the write against the stored universe without its own stored row. + - `conditionalRequired` is still refused at the save door's schema step, before this gate, as a key retired in protocol 17; `os build` judges it as a field-rule slot as before. + - Option `visibleWhen` and the object's own action predicates are still not judged at this door. `os build` judges them, and the door does not, as before. + - `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write. + - Measured before crossing: every field-rule slot this repository ships has 0 refusals and 0 advisories, at the build and at the door. That is 9 slots on 8 fields of 3 objects (examples: 8 on `showcase_invoice` and `showcase_invoice_line`, three of them `parent`-scoped; the platform: 1 on `sys_permission_set`), over the 118 objects this repository ships. + - No public export or signature moves. `validateStackExpressions(stack)` keeps its signature, and no registry entry changes: the expression rule already declared `object`. + + +- 3d91885: fix(lint)!: the object save door refuses a validation rule whose predicate `os build` refuses (#22032) + + Clause-②: no (narrowing) + + `formulas.mdx` says the same `validateExpression` validator backs `os build` and metadata registration. For a validation rule's predicates it did not, at the object save door. A rule whose `condition` called an unregistered function, such as `sqrt(record.amount) > 1`, or read a bare field, such as `amount > 1`, was refused by `os build` at error, but `PUT /api/v1/meta/object/:name` answered 200 and stored it. The rule then faulted on every write it judged. + + The runtime publish gate now runs the build's validation-rule check on an object write. The build's expression rule (`validateStackExpressions`) was already on the object door for formula fields alone. On an object write it now also runs its validation-rule pass: each `validations[]` rule's `condition` and a `conditional` rule's `when`, plus the null-guard check over every predicate the rule carries, its nested `then` and `otherwise` rules included. The door's verdict is the build's finding: the same rule id (`expression-invalid`), location (`object 'NAME' · validation 'RULE'`, or `… validation rule 'RULE' then → 'CHILD'` for a nested predicate), message and hint. + + **BREAKING — what moves for consumers.** + + - An object write in publish mode answered 200 for a validation rule whose predicate the shared validator refuses. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that rule. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`). + - The verdict is the one `os build`, `os validate` and `os lint` already gave: an unknown function, a field the object does not declare, a bare field reference (`amount` instead of `record.amount`), a syntax error, an ordering or arithmetic operator applied to a nullable field with no `!= null` guard (`has()` is no guard here), and the other errors in the build's validation-rule check. Its warnings now ride the save response as advisories. + + **Remedy.** Fix the predicate: the message names the unknown function or field, or the unguarded operand, and the position, as `os build` already requires. Qualify field reads as `record.FIELD`, use one of the functions `introspectScope` lists, and guard a nullable operand with `record.FIELD != null && …`. Saving it as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged. + + **Unchanged.** + + - Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps loading until it is next saved. At that save the gate judges it, because the differential compares the write against the stored universe without its own stored row. + - The other expressions an object carries are still not judged at this door: the field-rule slots (`requiredWhen`, `readonlyWhen`, `conditionalRequired`, `visibleWhen`), option `visibleWhen`, and the object's own action predicates. `os build` judges them, and the door does not, as before. + - `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write. + - Measured before crossing: every validation rule this repository ships has 0 refusals and 0 advisories, at the build and at the door. That is 21 rules carrying 13 predicates on 10 objects: examples 11 predicates on 7 objects, and the platform objects 2 on 3 (one rule on `sys_user` carries no predicate). + - No public export or signature moves. `validateStackExpressions(stack)` keeps its signature, and no registry entry changes: the expression rule already declared `object`. + + +- ace0a53: feat(lint)!: `relationship/master-detail-required` refuses the three unsafe master-reference shapes at `error` on a `controlled_by_parent` object (#9139) + + Clause-②: no (narrowing) + + A `controlled_by_parent` detail derives all of its record access from the master its `master_detail` reference names (ADR-0055). Three declarable shapes of that reference leave the security gate as the only thing refusing a detail record saved without its master, because record validation never checks a field that is not `required` and skips `readonly` and `system` fields before its required check: + + 1. `required` absent, or `required: false`; + 2. `required: true` with `readonly: true`; + 3. `required: true` with `system: true`. + + A record that lands without its master anyway is readable by nobody, and every later write to it by id is refused. Until now `relationship/master-detail-required` was a `warning` with the predicate "`required` is not `true`", on every object, so shapes 2 and 3 drew no finding at any severity. The maintainer ruling of 2026-08-16 (Direction 1) scheduled the promotion for the v18 boundary, scoped to `controlled_by_parent`. + + **BREAKING — what moves for consumers.** + + - `os lint` reports each of the three shapes at `error` when the object declares `sharingModel: 'controlled_by_parent'`, located at the defect (`…fields.FIELD.required`, `.readonly` or `.system`). It covers every `master_detail` field of such an object, the same scope the builder's `required: true` force already applies. `os lint` therefore exits non-zero on such a stack, and the metadata-generation rubric (`scoreMetadata`) weighs the finding as an error and marks the stack `valid: false`. + - `@objectstack/spec` gains the step-18 semantic migration entry `cbp-master-detail-required-lint-error`, so `os migrate meta` across protocol 18 prints the prescription below. + + **Remedy — the v18 upgrade-checklist line.** On every object with `sharingModel: 'controlled_by_parent'`, give each `master_detail` reference `required: true` and remove any `readonly: true` or `system: true` from it. `os lint` now refuses the missing-`required`, `required` + `readonly` and `required` + `system` shapes there at `error` (`relationship/master-detail-required`). An object authored through `ObjectSchema.create` already gets `required: true` when the key is omitted, so the edit there is dropping the flag. + + **Unchanged.** + + - On every object that is not `controlled_by_parent` the rule is exactly as before: a `warning` for a `master_detail` without `required: true`, the same message and fix, and no finding for the two flagged shapes. + - The rule is not in the authoring-rule registry. `os build`, `os validate` and the metadata save door do not run it, so a stack carrying one of the shapes still builds and publishes. Only `os lint`'s exit code and the generation rubric move. + - Runtime is untouched. The security gate keeps refusing an insert that omits the master FK on these shapes and keeps resolving the master for metadata already at rest, and stored metadata is neither rewritten nor refused on load. + - No export or signature moves in either package. + - Measured before crossing, at `b04a5295f`: 129 authored objects across the example apps, the platform, plugin and service objects and the CLI's golden eval corpus. 7 of them are `controlled_by_parent`, and 0 draw the new `error`. + + + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + - @objectstack/formula@17.8.0 + - @objectstack/sdui-parser@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/lint/package.json b/packages/lint/package.json index 8882f4b5d09..cf16e99bfd8 100644 --- a/packages/lint/package.json +++ b/packages/lint/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/lint", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Static, build-time validation for an ObjectStack metadata graph — dashboard widget bindings, CEL/predicate expressions, and more. Pure (stack) => Issue[] functions shared by the CLI's `os validate` and any other consumer (e.g. AI authoring). Depends on @objectstack/spec; never on a runtime.", "type": "module", diff --git a/packages/mcp/CHANGELOG.md b/packages/mcp/CHANGELOG.md index 5d90b9934ab..76e6a8d38cc 100644 --- a/packages/mcp/CHANGELOG.md +++ b/packages/mcp/CHANGELOG.md @@ -1,5 +1,33 @@ # @objectstack/plugin-mcp-server +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/formula@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 838fc7bb294..d94447e4301 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/mcp", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "ObjectStack as an MCP server — exposes your app's objects (and AI tools) over the Model Context Protocol (stdio + Streamable HTTP)", "type": "module", diff --git a/packages/metadata-core/CHANGELOG.md b/packages/metadata-core/CHANGELOG.md index dc9b432115f..21e2e6d0fc7 100644 --- a/packages/metadata-core/CHANGELOG.md +++ b/packages/metadata-core/CHANGELOG.md @@ -1,5 +1,36 @@ # @objectstack/metadata-core +## 17.8.0 + +### Patch Changes + +- 56c8844: `@objectstack/spec/ui` now exports the rule that decides which forms a `view` body opens to anonymous intake, so a console reads "published" from the same rule the server's anonymous form doors serve + + Clause-②: yes (widening) + + - **New on `@objectstack/spec/ui`:** `publicFormSlug`, `anonymousFormIntakeSlug`, `anonymousFormIntakeCandidates`, `anonymousFormIntakeSlugs` and the `AnonymousFormIntakeCandidate` type. They lived only in `@objectstack/metadata-core`, which a browser console should not depend on. They are pure functions with no imports, beside the `SharingConfigSchema` they read. + - **What they decide is unchanged.** A form is open when its `sharing` has `enabled === true`, `allowAnonymous === true` and a non-empty `publicLink`. The scan covers the same three shapes in the same order: the nested `form`, every `formViews` entry, then the `config` of a `viewKind: 'form'` item. + - **`@objectstack/metadata-core` re-exports the same functions** from `@objectstack/spec/ui`. They are the spec's own bindings, not wrappers or copies, so there is still one copy of the rule. Its exports, names and types are unchanged, and `@objectstack/rest` and `@objectstack/metadata-protocol` keep importing from it. Its built output now loads `@objectstack/spec/ui` to get them. + - **Not covered by the new export:** whether another metadata layer withdraws a form (`anonymousFormIntakeWithdrawnIn`), and whether the deployment's tenancy posture lets the form take an anonymous submission (`anonymousFormIntakeUnavailability`). These two read server state and stay in `@objectstack/metadata-core`. `anonymousFormObjectName`, which names the object a form submits into, stays there beside them; it is a pure read of the form and the view, not of server state. A form the new functions call open can still be withheld by a withdrawal in another layer or by the posture. +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/metadata-core/package.json b/packages/metadata-core/package.json index 3fd62a36809..8461da09702 100644 --- a/packages/metadata-core/package.json +++ b/packages/metadata-core/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-core", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Metadata Repository contracts: types, canonicalization, errors, interface (ADR-0008).", "type": "module", diff --git a/packages/metadata-fs/CHANGELOG.md b/packages/metadata-fs/CHANGELOG.md index f7f3a34ce25..81bfbc93742 100644 --- a/packages/metadata-fs/CHANGELOG.md +++ b/packages/metadata-fs/CHANGELOG.md @@ -1,5 +1,12 @@ # @objectstack/metadata-fs +## 17.8.0 + +### Patch Changes + +- Updated dependencies [56c8844] + - @objectstack/metadata-core@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/metadata-fs/package.json b/packages/metadata-fs/package.json index 499230b2d98..9c17dd3378e 100644 --- a/packages/metadata-fs/package.json +++ b/packages/metadata-fs/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-fs", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "FileSystemRepository: Node-only Repository implementation backed by JSON files and a JSONL change log (ADR-0008).", "type": "module", diff --git a/packages/metadata-protocol/CHANGELOG.md b/packages/metadata-protocol/CHANGELOG.md index 55f80584238..da4fae5af49 100644 --- a/packages/metadata-protocol/CHANGELOG.md +++ b/packages/metadata-protocol/CHANGELOG.md @@ -1,5 +1,216 @@ # @objectstack/metadata-protocol +## 17.8.0 + +### Minor Changes + +- 8caa131: A package's stored copy of a view container it ships overlays that package's shipped views, so a withdrawal saved in the copy holds at the anonymous form endpoints; the runtime save door refuses three copies it accepted before + + Clause-②: no (narrowing) + + + + **BREAKING** accept-set narrowing at the runtime save door, shipped as `minor` under the repo's launch-window convention for breaking changes, the grade the same door's earlier view container refusals shipped with. + + - **What was wrong.** The source loaders register a view container a package ships as `OBJECT.KEY` views for that package, whichever package owns the object. When that package stored a copy of the same container (a `PUT /api/v1/meta/view/NAME` of the container) and the object belonged to another code package, the copy expanded under its own name instead, as `OBJECT.CONTAINER.KEY` (a bare `list` as `OBJECT.CONTAINER`). So the copy overlaid none of the views its package ships: a form withdrawn from anonymous intake in the copy stayed open in the package's shipped form of that name, and the anonymous form endpoints kept serving it. + - **What it does now.** A package's stored copy of a container that package ships, bound to the same object, expands as the loaders expand the shipped container: each member is served under the loaders' name, `OBJECT.KEY`, in the copying package's own slot on the view list and on the by-name read naming that package. So a withdrawal saved in the copy holds at the anonymous form endpoints. The copy still declares no default view for an object another package owns. Any other container on another package's object keeps expanding under its own name, unchanged. + - **The by-name read on an unscoped kernel.** On a kernel with no environment id, a stored container's expanded views are also registered in the schema registry, under the bare name. A view whose name another package also ships is no longer registered there: the registry answered that bare entry ahead of the other package's own view, so `getMetaItem` naming the other package served this container's view. Every kernel's by-name read already serves such a view from its stored row, for its own package and for a read that names no package. Two packages that ship one container, with one of them storing a copy, were affected before this change too. + + **What is refused now.** `saveMetaItem`, which `PUT /api/v1/meta/view/NAME` and the dispatcher's metadata save both call, judges a copy at the names it now expands to, so three copies it accepted before are refused with `VALIDATION_ERROR` / 400, before anything is stored. Each is a package's copy of a container it ships on another package's object: + + - The copy adds a bare `list` whose name, `OBJECT.default`, only the other package ships. Before: accepted, served as `OBJECT.CONTAINER`. After: refused, naming the package that ships `OBJECT.default`. + - The copy adds a keyed member (a `formViews` or `listViews` entry, a named `list`, or a `form`) whose name, `OBJECT.KEY`, only the other package ships. Before: accepted, served as `OBJECT.CONTAINER.KEY`. After: refused, naming the package that ships `OBJECT.KEY`. + - Another stored container, saved under a different name, already expands a name the copy now expands. Before: accepted. After: refused, naming that stored container. + + **The fix.** For the first two, give the added member a key of its own that no package ships and no stored container expands, or save a view item (`name`, `object`, `viewKind`, `config`) under that name to override the other package's view. For the third, add the view as a member of the stored container that already expands the name, or save a view item under that name. + + **What still saves.** A copy that keeps the members its package's shipped container has, their contents edited, under the container's own name. A copy that adds a member under a key no package ships and no stored container expands. A view item under any of these names. Every container that is not a copy of its own package's shipped container, as before. + + **Rows stored before this change.** They keep their bytes. A stored copy of a container its package ships, on another package's object, is now served under the loaders' names (`OBJECT.KEY`) instead of `OBJECT.CONTAINER.KEY`, so it overlays the package's shipped views from the next read on, with no re-save. A reference to one of its old names (a navigation `viewName`, a form action `target`) no longer resolves; point it at `OBJECT.KEY`. A new save of a stored copy in one of the refused shapes, a re-save included, is refused until its body stops colliding. Delete stays open. +- 1fb274e: `ObjectStackProtocolImplementation.declinesStoredRow(type, name)` is now public, so a door that serves a stored row out of the layered read can ask the same decision the reads make + + Clause-②: yes (widening) + + - The method answers `true` for exactly the names whose stored `sys_metadata` row the active reads (`getMetaItem`, the list, and the `effective` layer of `getMetaItemLayered`) do not adopt: a flow name a managed package ships (the answer `isShippedFlowName` gives), and a datasource name the host registers from code (one an installed package declares in `*.datasource.ts`, or the host's `default`). Every other type and name answers `false`. + - The `GET /meta/:type/:name/published` doors in `@objectstack/rest` and `@objectstack/runtime` now ask this method in place of `isShippedFlowName`. A door asks it, and does not restate either half or the host's code-datasource set. + - `isShippedFlowName` stays public and unchanged. + - The only change to the public surface is this one added method. No signature, schema or accept set changes, and no behaviour of this package changes. +- f85a83b: fix(lint)!: the object save door refuses a formula field whose expression `os build` refuses (#22019) + + Clause-②: no (narrowing) + + `formulas.mdx` says the same `validateExpression` validator backs `os build` and metadata registration. At the object save door it did not. A formula field calling an unregistered function, such as `sqrt(record.amount)`, was refused by `os build` as an unknown function, but `PUT /api/v1/meta/object/:name` answered 200, stored it, and the field read `null` on every row. + + The runtime publish gate now runs the build's own formula check on an object write. The registry entry for the build's expression rule (`validateStackExpressions`) declared the flow, action and hook writes and never the object write, so the gate never dispatched it there. It now declares `object` as well, for one of its passes: a formula field's `expression`. The door's verdict is the build's finding: the same rule id (`expression-invalid`), location (`object 'NAME' · field 'FIELD' expression`), message and hint. + + **BREAKING — what moves for consumers.** + + - An object write in publish mode answered 200 for a formula field whose expression the shared validator refuses. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that field's `expression`. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`). + - The verdict is the one `os build`, `os validate` and `os lint` already gave: an unknown function, a field the object does not declare, a bare field reference (`amount` instead of `record.amount`), and the other errors in the build's formula check. Its warnings now ride the save response as advisories, as they already did for a flow write. + + **Remedy.** Fix the expression: the message names the unknown function or field and the position, as `os build` already requires. Use one of the functions `introspectScope` lists, qualify field reads as `record.FIELD`, or compute the value in a stored field and reference it. Saving it as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged. + + **Unchanged.** + + - Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps reading, with the formula still `null`, until it is next saved. At that save the gate judges it, because the differential compares the write against the stored universe without its own stored row. + - The other expressions an object carries are still not judged at this door: validation-rule predicates, the field-rule slots (`requiredWhen`, `readonlyWhen`, `conditionalRequired`, `visibleWhen`), option `visibleWhen`, and the object's own action predicates. `os build` judges them, and the door does not, as before. Each needs its own crossing, measured over the stored corpus first. + - `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write. + - Measured before crossing: every formula field this repository ships has 0 refusals and 0 advisories at the door. That is 29 fields on 28 objects: examples 7 on 6, and the platform `display_title` formulas 22 on 22. + - No public export or signature moves. `validateStackExpressions(stack)` keeps its signature. The registry entry reaches the passes through an internal function that is not on the package's entry. The built entry declarations differ only in one doc comment, on `AuthoringRuleContext.runtimeWriteType`. + + +- f2a45db: fix(lint)!: the object save door refuses a field-rule slot whose predicate `os build` refuses (#22032) + + Clause-②: no (narrowing) + + `formulas.mdx` says the same `validateExpression` validator backs `os build` and metadata registration. For a field's rule slots it did not, at the object save door. A field whose `requiredWhen` read a bare field, such as `amount > 1`, or whose `visibleWhen` called an unregistered function, such as `sqrt(record.amount) > 1`, was refused by `os build` at error, but `PUT /api/v1/meta/object/:name` answered 200 and stored it. + + The runtime publish gate now runs the build's field-rule-slot check on an object write. The build's expression rule (`validateStackExpressions`) was already on the object door for formula fields and validation-rule predicates. On an object write it now also judges each field's `requiredWhen`, `readonlyWhen` and `visibleWhen` the way the build does, with the build's three gates on them: the `parent` gate, the null-guard check over `requiredWhen`, and the refusal of a `requiredWhen` or `readonlyWhen` that reads through a reference field. The door's verdict is the build's finding: the same rule id (`expression-invalid`), location (`object 'NAME' · field 'FIELD' SLOT`), message and hint. + + **BREAKING — what moves for consumers.** + + - An object write in publish mode answered 200 for a field whose `requiredWhen`, `readonlyWhen` or `visibleWhen` the shared validator refuses. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that slot. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`). + - The verdict is the one `os build`, `os validate` and `os lint` already gave: an unknown function, a field the object does not declare, a bare field reference (`amount` instead of `record.amount`), a syntax error, a root a field-level rule never binds (such as `current_user`), a `parent` read on an object that does not declare exactly one `master_detail` relationship, an ordering or arithmetic operator in `requiredWhen` applied to a nullable field with no `!= null` guard, and a `requiredWhen` or `readonlyWhen` that reads through a reference field (`record.account.tier`, or `parent.REF.FIELD`). Its warnings now ride the save response as advisories. + - A detail object's `requiredWhen` or `readonlyWhen` that reads through one of its master's reference fields (`parent.REF.FIELD`) is judged whenever the master is in the write's context, and that includes a save of the master itself. So a master save can answer 422 with an issue located at a stored detail's field. Fix the detail's predicate, then save the master again. + + **Remedy.** Fix the predicate: the message names the unknown function or field, the unbound root, the unguarded operand or the reference read, and the position, as `os build` already requires. Qualify field reads as `record.FIELD`, use one of the functions `introspectScope` lists, guard a nullable operand in `requiredWhen` with `record.FIELD != null && …`, and move a check that must read through `record.REF` into a `validations[]` `script` rule, whose `condition` is read one hop through a reference; a read through `parent.REF` has no such surface, so read a column the master declares instead (denormalise the value onto it). Saving it as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged. + + **Unchanged.** + + - Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps loading until it is next saved. At that save the gate judges it, because the differential compares the write against the stored universe without its own stored row. + - `conditionalRequired` is still refused at the save door's schema step, before this gate, as a key retired in protocol 17; `os build` judges it as a field-rule slot as before. + - Option `visibleWhen` and the object's own action predicates are still not judged at this door. `os build` judges them, and the door does not, as before. + - `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write. + - Measured before crossing: every field-rule slot this repository ships has 0 refusals and 0 advisories, at the build and at the door. That is 9 slots on 8 fields of 3 objects (examples: 8 on `showcase_invoice` and `showcase_invoice_line`, three of them `parent`-scoped; the platform: 1 on `sys_permission_set`), over the 118 objects this repository ships. + - No public export or signature moves. `validateStackExpressions(stack)` keeps its signature, and no registry entry changes: the expression rule already declared `object`. + + +- 3d91885: fix(lint)!: the object save door refuses a validation rule whose predicate `os build` refuses (#22032) + + Clause-②: no (narrowing) + + `formulas.mdx` says the same `validateExpression` validator backs `os build` and metadata registration. For a validation rule's predicates it did not, at the object save door. A rule whose `condition` called an unregistered function, such as `sqrt(record.amount) > 1`, or read a bare field, such as `amount > 1`, was refused by `os build` at error, but `PUT /api/v1/meta/object/:name` answered 200 and stored it. The rule then faulted on every write it judged. + + The runtime publish gate now runs the build's validation-rule check on an object write. The build's expression rule (`validateStackExpressions`) was already on the object door for formula fields alone. On an object write it now also runs its validation-rule pass: each `validations[]` rule's `condition` and a `conditional` rule's `when`, plus the null-guard check over every predicate the rule carries, its nested `then` and `otherwise` rules included. The door's verdict is the build's finding: the same rule id (`expression-invalid`), location (`object 'NAME' · validation 'RULE'`, or `… validation rule 'RULE' then → 'CHILD'` for a nested predicate), message and hint. + + **BREAKING — what moves for consumers.** + + - An object write in publish mode answered 200 for a validation rule whose predicate the shared validator refuses. It now answers `422 INVALID_METADATA`, with an `expression-invalid` issue located at that rule. This covers `PUT /api/v1/meta/object/:name` (and `saveMetaItem` in publish mode), the promotion of a draft (`POST /api/v1/meta/object/:name/publish`, `publishMetaItem`), and a package draft publish (`publishPackageDrafts`). + - The verdict is the one `os build`, `os validate` and `os lint` already gave: an unknown function, a field the object does not declare, a bare field reference (`amount` instead of `record.amount`), a syntax error, an ordering or arithmetic operator applied to a nullable field with no `!= null` guard (`has()` is no guard here), and the other errors in the build's validation-rule check. Its warnings now ride the save response as advisories. + + **Remedy.** Fix the predicate: the message names the unknown function or field, or the unguarded operand, and the position, as `os build` already requires. Qualify field reads as `record.FIELD`, use one of the functions `introspectScope` lists, and guard a nullable operand with `record.FIELD != null && …`. Saving it as a draft (`mode: 'draft'`) is still allowed, because drafts are never gated; publishing that draft is judged. + + **Unchanged.** + + - Stored rows are not migrated, and they are not refused on read. An object stored before this change keeps loading until it is next saved. At that save the gate judges it, because the differential compares the write against the stored universe without its own stored row. + - The other expressions an object carries are still not judged at this door: the field-rule slots (`requiredWhen`, `readonlyWhen`, `conditionalRequired`, `visibleWhen`), option `visibleWhen`, and the object's own action predicates. `os build` judges them, and the door does not, as before. + - `OS_ALLOW_UNLINTED_METADATA_WRITES=1` still turns a refusal into a logged write. + - Measured before crossing: every validation rule this repository ships has 0 refusals and 0 advisories, at the build and at the door. That is 21 rules carrying 13 predicates on 10 objects: examples 11 predicates on 7 objects, and the platform objects 2 on 3 (one rule on `sys_user` carries no predicate). + - No public export or signature moves. `validateStackExpressions(stack)` keeps its signature, and no registry entry changes: the expression rule already declared `object`. + + +- a543e24: `ObjectStackProtocolImplementation.revertStoredPackage({ packageId, organizationId?, actor? })` reverts a package's stored members to their published version + + Clause-②: yes (widening) + + - A Studio-authored package's members are `sys_metadata` rows bound by `package_id`. The method reads them through the same predicate `listDrafts` uses, which now has one statement shared by both reads. + - No stored row bound to the package: it answers `{ stored: false, discarded: [] }` and touches nothing. The package's members, if it has any, belong to the metadata service. An unprovisioned `sys_metadata` answers the same way. + - Stored rows, none of them published: it refuses with `RESOURCE_CONFLICT` / 409, "Package '…' has never been published, so there is no published version to revert to." Nothing is touched. + - Otherwise it removes every draft of the package, each in the scope it lives in, through the same per-draft step `discardPackageDrafts` takes. Each item then serves its published (active) row again. An item created after the last publish has no published row and is removed. A refused draft fails the call with that refusal's own code and status. + - The only change to the public surface is this one added method. `discardPackageDrafts` and `listDrafts` answer as before. + +### Patch Changes + +- b88c356: The remaining platform producers in these four packages now pass the explicit system opt-in (`{ isSystem: true }`) on their data-engine calls. Until now they reached the engine with no principal and no opt-in, and the security middleware let that through only because of its principal-less hand-off. + + Clause-②: no + + - **service-messaging, the inbox read state.** `listInbox` (and its unread total), the receipt read behind it, and mark-read / mark-all-read take the opt-in inside the service. Their scope is unchanged: every read of a user's rows is keyed on the user id the door derived from the session, the receipt a mark-read inserts is stamped with it, and the receipt it updates is one a user-keyed read returned. + - **service-messaging, `owner_of:` audiences.** The record read takes the opt-in, the same posture as the email lookup beside it. It reads only `id` and the owner fields, and only the owner id leaves the resolver. An `owner_of:` audience on an object whose sharing model is `private` now resolves its owner; before, it resolved to nobody. + - **service-messaging, the rest of the fan-out and the outboxes.** The `role:` and `team:` membership reads, the email and SMS recipient reads, the notification template read, the dedup lookup in `emit()`, and both outboxes' enqueue, ack and list. + - **service-storage.** `StorageMetadataStore.createFile` and `createSession` insert under the opt-in. The organization still reaches the driver beside it, so the stored organization is unchanged, and the file's `owner_id` is still the uploading user. + - **service-settings.** The `sys_secret` store the plugin builds (insert, get, update), and the read that verifies a rotation before the old secret is reaped. A store `update` now writes the `ciphertext` it is given; without a context the engine's read-only strip dropped it. No caller in this repository uses `update`. + - **metadata-protocol.** `SysMetadataRepository.getByHash`, `list`, `history` and the history replay of `watch()`. + - None of the gates the middleware runs before its hand-off applies to these calls. ⛔ No new export on any package entry, and no new elevation API. +- 1abfc58: The metadata door serves a code-defined datasource's code definition while a stored row under its name still exists + + Clause-②: no + + - `GET /api/v1/meta/datasource/:name`, the `GET /api/v1/meta/datasource` list and the `effective` layer of `GET /api/v1/meta/datasource/:name/layers` now skip a stored `sys_metadata` row under a datasource name the host registers from code: one an installed package declares in `*.datasource.ts`, or the host's `default`. They serve the in-memory code definition instead. The datasource admin door and the boot restore already serve that ("code wins on collision"). Before this change the stored row was served first, so the two doors answered with two different bodies for one name. + - The decision is made by name, through the same predicate the reads already ask for a shipped flow name. It never reads a row's `origin`. Every other type keeps ADR-0005's read order, in which the stored overlay wins. + - Unchanged: the row stays at rest and is still reported in the layered read's `overlay`. The read envelope stays `deletable: true` while the row exists, and `DELETE /api/v1/meta/datasource/:name` still removes it as the repair. A draft read (`state: 'draft'`, or the draft preview) is still answered from the draft row. A runtime datasource's stored row is served as before. + - The `/meta` and admin doors already refuse to write such a row. This change affects only how a row left from before that refusal is read. + - Not moved: `GET /api/v1/meta/datasource/:name/published` still serves the stored row, which is the active overlay row that route describes. + - ⛔ No public export, signature, schema or accept-set change. The built entry declarations gain two `private` member names on `ObjectStackProtocolImplementation`. +- db87a02: fix(metadata-protocol): another package's withdrawal of a form holds at the anonymous form endpoints, whatever packages' copies of a view container are saved + + Clause-②: no + + - **What was wrong.** Where packages ship the same view container, the view list (`getMetaItems` for `view`) served one item for each name a saved environment-wide copy of that container expands: the copy's own expansion. Every other package's item of that name, shipped or saved, was left out. The anonymous form endpoints judge a withdrawal against the environment-wide view list, so they could miss another package's withdrawal of such a form. + - **What it does now.** The view list serves each package its own item of such a name: + - a package's saved copy of the container serves that package's item of each name it expands; + - a package-less saved copy stands in for every package that has no copy of its own (ADR-0048); + - any other package keeps its own item. + + So another package's withdrawal of a form holds at the anonymous form endpoints, whatever packages' copies of the container are saved. The organization-scoped save check reads the same list, so it judges each package's item too. + - **A stored view row of exactly such a name** keeps its own package's slot only. A package-less row still serves every package's slot. Before, any package's row of the name kept every package's copy expansion of it out of the list. + - **The by-name read agrees.** `getMetaItem` naming a package serves the item that package's slot in the list serves. Where no copy belongs to that package, a package-less copy now stands in for it. A list scoped to a package (`GET /api/v1/meta/view?package=`) serves the same item in each slot the package lists. A package-less copy adds no item to that list. + - **What does not change.** Within one package, a later expansion of a name still replaces an earlier one, and the save door's view container collision check is unchanged. A by-name read that names no package answers as before. No key, export, status or error code changes. +- 6befe19: A metadata row stored with no `checksum` can be edited and removed through the metadata door (#21978) + + Clause-②: no + + - `SysMetadataRepository` serves a `sys_metadata` row that carries no `checksum` as the hash of its stored body, but its `put` and `delete` compared the caller's parent with the raw column (`null`). So `PUT` and `DELETE /api/v1/meta/:type/:name` answered `409 METADATA_CONFLICT` ("Expected parent … but current is null") for every such row, with `If-Match` set to the version the door served and with no `If-Match` (last-write-wins) alike. A publish over such a row was refused the same way, as were the rollback and commit-revert doors, which take their parent from the same read. The datasource admin door stored such rows before it stamped them. + - `put` and `delete` now accept the version such a row is served as. A `null` parent still matches it, and a row with a `checksum` is judged exactly as before. A stale version is still refused with `409 METADATA_CONFLICT`, and the refusal now names the row's served version as the current one instead of `null`. + - The next write stamps the row's `checksum`, as every write does. Stored rows are not rewritten. + - Publishing a draft row stored with no `checksum` now also removes that draft row. Before, the post-promotion cleanup was refused by the same lock and the draft stayed pending, with nothing reported. +- 2015c54: A by-name metadata read that names no package now wears the package of the body it serves + + Clause-②: no + + - **What was wrong.** `getMetaItem` with no `packageId` (behind `GET /api/v1/meta/TYPE/NAME` when no package is named) merges the registry artifact's protection envelope, `_packageId`, `_packageVersion` and `_provenance`, over the body it serves. With no package named, that envelope was the first-registered package's. When two installed packages ship one name and the body served was the other package's, the answer carried that body under the wrong package. Two cases were measured. In the first, both packages ship a view container and one of them stores a copy of it: the read served the copy's view and named the first-registered package. In the second, a stored row of the name is bound to one package. The answer's top-level `packageId` / `provenance` / `packageVersion` fields, which are read off the served item, said the same wrong thing. + - **What it does now.** With no package named, the envelope is looked up at the package the served item is bound to. That is the stored row's package, the package of the container copy that expands the name, or the `_packageId` of the MetadataService or registry item. This is the rule the `GET /api/v1/meta/TYPE` list already applies to each item it serves, so the list and the by-name read now give one envelope for one served body. + - **Unchanged.** Which body the read serves. A read naming a package. The lock family and the `lock` / `editable` / `deletable` envelope, which still come from the item-lock resolution over the read's own address. A served item bound to no package (a package-less stored row or copy, or a registry entry with no package) keeps the package-less lookup it had, so for a name only one package ships, a tenant's package-less overlay still wears that package's envelope. The layered read (`/layers`) is not changed. + - ⛔ No public export, signature, schema or accept-set change. Nothing is accepted or refused differently. +- ae97841: A by-name view read naming a package now serves that package's view when another package's stored row has the same name + + Clause-②: no + + - **What was wrong.** Two installed packages can ship a view of one name, and a stored `sys_metadata` view row can be bound to one of them. The registry hydration registered that row under the view's bare name. The registry answers the bare name ahead of every package's own entry, whichever package the read names. So `getMetaItem` naming the OTHER package found no row of its own and served this row's body at its registry step, under the other package's envelope. That is the read behind `GET /api/v1/meta/view/NAME?package=…`. Meanwhile the list's slot for that package served its own view. This was measured on an unscoped kernel after a save, and on either kernel after a cold boot, because `loadMetaFromDb` hydrates through the same door on every kernel. + - **What it does now.** A view row bound to one package is not registered under a name another package ships. This is the shape the view-container expansion registration already takes. The reads answer the row from the row itself: the read naming its own package, the read naming no package, and the list's slot for that package. The read naming the other package serves that package's own view and envelope. The delete's registry heal also stops re-registering a metadata-service view baseline bound to one package under such a name. + - **Scope: `view` only.** Every other type registers as before: a row bound to one package, of a name two packages ship, keeps the bare entry with its own body and its own package's envelope. A package-less view row, and a view row of a name that only its own package ships or that no package ships, also register as before. The environment-scoped kernel's answer after a save is unchanged, because it registers nothing on a save. + - ⛔ No public export, signature, schema or accept-set change. Nothing is accepted or refused differently. The built entry declarations gain one `private` member name on `ObjectStackProtocolImplementation`. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [f2a45db] +- Updated dependencies [3d91885] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/lint@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/metadata@17.8.0 + - @objectstack/formula@17.8.0 + - @objectstack/sdui-parser@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/metadata-protocol/package.json b/packages/metadata-protocol/package.json index 693a62336ba..411dadeb776 100644 --- a/packages/metadata-protocol/package.json +++ b/packages/metadata-protocol/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata-protocol", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "ObjectStack metadata management protocol: sys_metadata CRUD, draft/publish, locks, package ownership, diagnostics (ADR-0076).", "type": "module", diff --git a/packages/metadata/CHANGELOG.md b/packages/metadata/CHANGELOG.md index c561e1b82fb..57d0248fc0b 100644 --- a/packages/metadata/CHANGELOG.md +++ b/packages/metadata/CHANGELOG.md @@ -1,5 +1,34 @@ # @objectstack/metadata +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/metadata-fs@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/metadata/package.json b/packages/metadata/package.json index 1cbe5edd757..9044bdb5326 100644 --- a/packages/metadata/package.json +++ b/packages/metadata/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/metadata", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Metadata loading, saving, and persistence for ObjectStack", "type": "module", diff --git a/packages/objectql/CHANGELOG.md b/packages/objectql/CHANGELOG.md index c3b84440ee1..b464e2d6f37 100644 --- a/packages/objectql/CHANGELOG.md +++ b/packages/objectql/CHANGELOG.md @@ -1,5 +1,60 @@ # @objectstack/objectql +## 17.8.0 + +### Patch Changes + +- 1920cf3: fix(objectql): the lifecycle reaper and archiver no longer partition an object with no tenant column by organization + + A tenant-scope `lifecycle.retention_overrides` entry gives one organization its own retention window, and the reaper and the archiver apply it by partitioning the object's rows on `organization_id`: one pass for that organization's rows, then a global pass for everyone else's. On an object that has no `organization_id` column — one declaring `systemFields: { tenant: false }`, such as the deployment-level platform tables (`sys_job`, `sys_job_run`, `sys_job_queue`, `sys_flow_dispatch`, `sys_migration`, `sys_migration_journal`, `sys_presence`), or any other object the registry injects no tenant column into and whose author declares none — both passes named a column the table does not have. The SQL driver refused them (`INVALID_FILTER`), the sweep reported the object in its errors, and the table's retention stopped. + + Such an object now has no tenant partition, the answer a federated object already got: the sweep runs its one global pass at the global window. No row of it belongs to an organization, so a tenant override naming it has nothing to select, and it is not applied. An object that has the column keeps its per-tenant windows unchanged. +- f85a83b: fix(objectql): a formula field that does not evaluate is logged once per object and field, instead of reading `null` in silence (#22019) + + A formula the engine cannot evaluate reads `null`, on `find`, on `findOne` and on the write response. Before this change nothing said why. A formula calling an unregistered function (`sqrt(record.amount)`) read `null` on every row with no log line anywhere. ADR-0032 says a call site must not silently swallow an expression fault. + + The engine now reports the fault through its logger at `warn`, once per (object, field) per engine instance, however many rows and reads hit it. The line names the object, the field and the evaluator's error (kind and first line; the full message is in the log metadata). It also says where the repair is: `os validate` or a re-save of the object refuses an expression-level fault with a located message, and a fault that depends on a record's values needs a guard on the operands it reads. + + Unchanged: the field still reads `null`, because what a read returns is protocol. `evaluateFormulaField`, the hook-side helper with no engine, still returns `null` without a log line. The built entry declarations gain three `private` member names on `ObjectQL`. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [b88c356] +- Updated dependencies [1abfc58] +- Updated dependencies [db87a02] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [6befe19] +- Updated dependencies [8caa131] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [2015c54] +- Updated dependencies [f2a45db] +- Updated dependencies [3d91885] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [ae97841] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-protocol@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/metadata@17.8.0 + - @objectstack/formula@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/objectql/package.json b/packages/objectql/package.json index d27e7e2baa6..19422274d65 100644 --- a/packages/objectql/package.json +++ b/packages/objectql/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/objectql", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Isomorphic ObjectQL Engine for ObjectStack", "main": "dist/index.js", diff --git a/packages/observability/CHANGELOG.md b/packages/observability/CHANGELOG.md index 879f1f31ccd..c0fa8f0b702 100644 --- a/packages/observability/CHANGELOG.md +++ b/packages/observability/CHANGELOG.md @@ -1,5 +1,28 @@ # @objectstack/observability +## 17.8.0 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/observability/package.json b/packages/observability/package.json index c1a2431240c..08d620eff7b 100644 --- a/packages/observability/package.json +++ b/packages/observability/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/observability", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Observability contracts and exporters for ObjectStack — MetricsRegistry, ErrorReporter, Logger plus noop/console/OTLP-HTTP exporters. Deployment-target neutral; runtime and services depend on this so the same instrumentation works on Cloudflare Workers, Node, and self-hosted Kubernetes.", "type": "module", diff --git a/packages/platform-objects/CHANGELOG.md b/packages/platform-objects/CHANGELOG.md index cc1017c1af9..00224aebdb3 100644 --- a/packages/platform-objects/CHANGELOG.md +++ b/packages/platform-objects/CHANGELOG.md @@ -1,5 +1,107 @@ # @objectstack/platform-objects +## 17.8.0 + +### Minor Changes + +- 1920cf3: feat(platform-objects,service-automation,service-realtime)!: seven deployment-level platform tables lose their injected organization column, and reading them needs `manage_platform_settings` (ADR-0131 D7) + + Clause-②: no (narrowing) + + + + **BREAKING**, shipped as `minor` under the repo's launch-window convention for breaking changes (Changesets pre mode is not on yet). + + `sys_job`, `sys_job_run`, `sys_job_queue`, `sys_flow_dispatch`, `sys_migration`, `sys_migration_journal` and `sys_presence` hold deployment-level state. No writer attributes a row of any of them to an organization: every write is a system-context write whose row names none, and nothing writes `sys_presence` through ObjectQL at all. So the injected `organization_id` column only ever held NULL. ADR-0131 D7 takes it off: each object now declares `systemFields: { tenant: false }`. + + With no column there is no tenant wall, so these tables are governed by object permission. Each also declares `requiredPermissions: ['manage_platform_settings']`. Without that gate, a walled deployment's `organization_admin`, whose grant carries the superuser bits on every object, would read every other organization's job errors, queued payloads, dispatch keys and migration traces. + + **What moves for consumers.** + + - **The column.** `organization_id` is no longer a field of these seven objects. A filter, list-view column, report grouping, formula or seed key naming it on one of them is now an unknown field. Delete the reference: no organization owns a row of these tables. + - **Who reads, on a walled posture** (`group` or `isolated`). Before: the wall compared the NULL column to the caller's organization, so every reader got zero rows, platform administrators included (unless the deployment declared the table platform-global, which stood the wall down). Now: a principal holding `manage_platform_settings` (platform administrators hold it) lists every row; anyone else is refused `403 PERMISSION_DENIED`. + - **Who reads, on the `single` posture.** Before: any principal with a read grant on the object read every row, an organization administrator included. Now: only a principal holding `manage_platform_settings` reads; an organization administrator who is not a platform administrator is refused `403 PERMISSION_DENIED`. Grant the capability to an operator who needs these tables. + + **Unchanged.** Every platform writer and reader of these tables uses a system context, which no capability gate applies to, so job scheduling, the queue, flow dispatch, migration flags and the migration journal behave as before. The physical unique indexes are unchanged: none of these objects declares an organization-scoped one. + + **Existing databases.** Schema sync only adds, so the physical `organization_id` column stays on each existing table (with its index, where the deployment indexed it), and the boot drift report names it orphaned. By the writer census it holds only NULL, so dropping it loses nothing: `os migrate apply --allow-destructive` drops it, the remedy the drift report names. + +### Patch Changes + +- 77a94d8: "Add Member" is offered only to a platform administrator, the one standing its endpoint admits. + + Clause-②: no + + - `sys_member`'s `add_member` toolbar action now declares `visible: 'current_user.isPlatformAdmin == true'`. `requiresFeature: 'organization'` composes onto it at parse time, so the served predicate reads `(current_user.isPlatformAdmin == true) && features.organization != false`. + - Its endpoint, `POST /api/v1/auth/organization/add-member`, has always admitted a platform administrator alone (ADR-0068) and answered every other caller, org owners and admins included, with 403 `PERMISSION_DENIED`. Before this change the button was still shown to every member of the organization. + - ⛔ Nothing you author changes. The endpoint and the callers it admits are unchanged, and no key, export or parameter is added. The action's label is unchanged. +- 879bd38: The user, OAuth-application and SSO-provider actions whose endpoint admits only a platform administrator are now offered only to a platform administrator. + + Clause-②: no + + - These thirteen actions now declare `visible: 'current_user.isPlatformAdmin == true'`, composed with their existing terms: + - `sys_user`: `ban_user`, `unban_user`, `unlock_user`, `create_user`, `set_user_password`, `impersonate_user` and `set_user_manager`; + - `sys_oauth_application`: `disable_oauth_application` and `enable_oauth_application`; + - `sys_sso_provider`: `register_sso_provider`, `register_saml_provider`, `request_domain_verification` and `verify_domain`. + - Where an action also carries `requiresFeature`, the feature gate composes onto it at parse time. For example, `ban_user` now serves `(current_user.isPlatformAdmin == true) && features.admin == true`. + - Each endpoint (`/api/v1/auth/admin/*`) has always admitted a platform administrator alone (ADR-0068) and answered every other caller, org owners and admins included, with 403 `PERMISSION_DENIED`. Before this change the buttons were still shown to those callers. + - `create_oauth_application`, `rotate_client_secret`, `delete_oauth_application` and `delete_sso_provider` are unchanged: their endpoints authorize the signed-in user or the record's owner, not the platform administrator. + - ⛔ Nothing you author changes. The endpoints and the callers they admit are unchanged, and no key, export or parameter is added. The actions' labels are unchanged. +- 1c563af: Setup's identity pages open on the tenant-wide list, not on the administrator's own rows. Before this, Setup → API Keys, Sessions, OAuth Applications, Identity Links and User Preferences opened each object's first declared list view, which was the caller-scoped "My …" view (`user_id = {current_user_id}`), so an administrator saw only their own keys, sessions, applications, links and preferences. + + Clause-②: no + + - On `sys_api_key`, `sys_session`, `sys_oauth_application`, `sys_account`, `sys_user_preference` and `sys_user`, the unscoped "All" view (`all_keys`, `all_sessions`, `all_apps`, `all_links`, `all_preferences`, `all_users`) is now declared first, and the caller-scoped view (`mine`, `me`) second. A route that names no view, such as a record page's object breadcrumb or the object switcher, now opens the "All" view. No view is added, removed or changed. + - The Setup entries `nav_api_keys`, `nav_sessions`, `nav_oauth_apps`, `nav_accounts` and `nav_user_preferences` now name that view with `viewName`, as `nav_users` already did. The Account app's Linked Accounts entry (`nav_account_linked`) now names `mine`, like the other Account entries, so neither app depends on the declared order. + - The "My …" views are still tabs on each page. The declared order decides which view opens, not which rows a caller may read: row-level security still scopes a member's rows. + - The generated translation bundles follow the new view order. No translated text changed. + - ⛔ No schema, parse, export or accept-set change. +- 51290bc: Form help and refusals an author reads no longer carry service-interface names, ruling dates or another product's ids + + Clause-②: no + + Wording only: no schema, key, type, export or error-code change. + + - The notify node's Template help (the `NotifyConfigSchema.template` describe and the Studio + inspector's copy in `@objectstack/service-automation`) names the deployment's default locale in + product words instead of `II18nService.getDefaultLocale()`, and drops its ruling date. + - `MANIFEST_ID_EXAMPLES` is now `com.acme.crm` and `org.example.help-desk` (was `com.steedos.crm` + and `org.apache.superset`). The package-id refusal opens with the headline + "Invalid package id 'VALUE'." and names the key in the sentence after it, so the headline alone + carries no JSON path; the rule, the examples and the suggestion follow unchanged in substance. A + caller that matched the old "on KEY. Expected reverse-domain notation" wording matches the + headline, or compares against `manifestIdRefusal()` by reference, instead. + - Ruling dates leave the describes Studio renders as form help: field `required` and `multiple`, + form-view field and section `visibleWhen`, section `collapsible` / `collapsed`, the redirect + arm's `submitBehavior.url`, and page `kind` / `source` (the ADR citations stay). They also + leave the refusals for padded grouping field names, `submitBehavior.url`, `features.*` in a + form-view predicate, and the four filter comparand refusals (null ordering comparand, + `{ $field }` in a list position, null list member, blank `$between` bound). Each sentence still + states the rule, why it exists and the repair. + - The email-template form's Identity section help says how senders address a template instead of + naming `IEmailService.sendTemplate`, in all four shipped locales. + - The action `description` help no longer ends in a dangling dash left behind by an earlier + strip: "(one dialog, not two —)" now reads "(one dialog, not two)". +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + - @objectstack/metadata-core@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/platform-objects/package.json b/packages/platform-objects/package.json index 6c379f03d34..1a868676a2f 100644 --- a/packages/platform-objects/package.json +++ b/packages/platform-objects/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/platform-objects", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Core platform object schemas for ObjectStack — identity, security, audit, tenant, and metadata objects", "main": "dist/index.js", diff --git a/packages/plugins/embedder-openai/CHANGELOG.md b/packages/plugins/embedder-openai/CHANGELOG.md index ba9ab3d8546..9e17782fdd9 100644 --- a/packages/plugins/embedder-openai/CHANGELOG.md +++ b/packages/plugins/embedder-openai/CHANGELOG.md @@ -1,5 +1,28 @@ # @objectstack/embedder-openai +## 17.8.0 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/plugins/embedder-openai/package.json b/packages/plugins/embedder-openai/package.json index f8a4f8ff8bf..af9c1111b74 100644 --- a/packages/plugins/embedder-openai/package.json +++ b/packages/plugins/embedder-openai/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/embedder-openai", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "OpenAI-compatible embedder for ObjectStack — works against OpenAI, 阿里通义 DashScope, 智谱 BigModel, 硅基流动 SiliconFlow, 火山引擎 Doubao, MiniMax, Ollama, and any drop-in OpenAI-shape endpoint.", "main": "dist/index.js", diff --git a/packages/plugins/knowledge-memory/CHANGELOG.md b/packages/plugins/knowledge-memory/CHANGELOG.md index ab27a799670..d7d7d80a01a 100644 --- a/packages/plugins/knowledge-memory/CHANGELOG.md +++ b/packages/plugins/knowledge-memory/CHANGELOG.md @@ -1,5 +1,31 @@ # @objectstack/knowledge-memory +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/service-knowledge@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/plugins/knowledge-memory/package.json b/packages/plugins/knowledge-memory/package.json index 369798c7ae6..ed81e6edd70 100644 --- a/packages/plugins/knowledge-memory/package.json +++ b/packages/plugins/knowledge-memory/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/knowledge-memory", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "In-memory knowledge adapter for ObjectStack (dev / test reference implementation).", "main": "dist/index.js", diff --git a/packages/plugins/knowledge-ragflow/CHANGELOG.md b/packages/plugins/knowledge-ragflow/CHANGELOG.md index 9bac1aef77a..183dd20678f 100644 --- a/packages/plugins/knowledge-ragflow/CHANGELOG.md +++ b/packages/plugins/knowledge-ragflow/CHANGELOG.md @@ -1,5 +1,31 @@ # @objectstack/knowledge-ragflow +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/service-knowledge@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/plugins/knowledge-ragflow/package.json b/packages/plugins/knowledge-ragflow/package.json index 99084b73e77..666ee4a4d2f 100644 --- a/packages/plugins/knowledge-ragflow/package.json +++ b/packages/plugins/knowledge-ragflow/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/knowledge-ragflow", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "RAGFlow knowledge adapter for ObjectStack — production-grade RAG via the Apache 2.0 RAGFlow REST API.", "main": "dist/index.js", diff --git a/packages/plugins/organizations/CHANGELOG.md b/packages/plugins/organizations/CHANGELOG.md index ead49479435..7f7b8faff48 100644 --- a/packages/plugins/organizations/CHANGELOG.md +++ b/packages/plugins/organizations/CHANGELOG.md @@ -1,5 +1,35 @@ # @objectstack/organizations +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [aa71c4d] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [4935c66] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/plugin-auth@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/plugins/organizations/package.json b/packages/plugins/organizations/package.json index 40010e257bf..64bc41a8597 100644 --- a/packages/plugins/organizations/package.json +++ b/packages/plugins/organizations/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/organizations", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Multi-organization runtime for ObjectStack — registers the `org-scoping` service that turns single-database row-level Organization isolation on: `organization_id` auto-stamp on insert, per-org seed replay, default-organization bootstrap, and the walled-posture membership-policy gate.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-approvals/CHANGELOG.md b/packages/plugins/plugin-approvals/CHANGELOG.md index f39d76519f9..e476ef9bc6f 100644 --- a/packages/plugins/plugin-approvals/CHANGELOG.md +++ b/packages/plugins/plugin-approvals/CHANGELOG.md @@ -1,5 +1,47 @@ # @objectstack/plugin-approvals +## 17.8.0 + +### Patch Changes + +- f0022c4: Setup → Approvals → Requests opens on every approval request, not on the requests pending on the administrator. Before this, the entry named no view, and `sys_approval_request` declared the caller-scoped "My Pending" view (`pending_approvers contains {current_user_id}`) first, so the console opened it. + + Clause-②: no + + - `sys_approval_request` now declares its unscoped "All" view (`all_requests`) first. "My Pending", "I Submitted" and "Completed" follow it in their previous order, still as tabs. A route that names no view, such as a record page's object breadcrumb or the object switcher, now opens "All". No view is added, removed or changed. + - The Setup entry `nav_approval_requests` now names `all_requests` with `viewName`, so it does not depend on the declared order. The Account app's Approvals entry opens the Approvals Inbox component and reads neither. + - The declared order decides which view opens, not which rows a caller may read. + - The generated translation bundles follow the new view order. No translated text changed. + - ⛔ No schema, parse, export or accept-set change. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/formula@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/plugins/plugin-approvals/package.json b/packages/plugins/plugin-approvals/package.json index cee65a289b5..3934303376f 100644 --- a/packages/plugins/plugin-approvals/package.json +++ b/packages/plugins/plugin-approvals/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-approvals", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Multi-step approval engine for ObjectStack — sys_approval_process + sys_approval_request + sys_approval_action + IApprovalService.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-audit/CHANGELOG.md b/packages/plugins/plugin-audit/CHANGELOG.md index 90a20b8ffa6..5def0ace57b 100644 --- a/packages/plugins/plugin-audit/CHANGELOG.md +++ b/packages/plugins/plugin-audit/CHANGELOG.md @@ -1,5 +1,40 @@ # @objectstack/plugin-audit +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/plugins/plugin-audit/package.json b/packages/plugins/plugin-audit/package.json index 31ddb803e23..1da1c3f654c 100644 --- a/packages/plugins/plugin-audit/package.json +++ b/packages/plugins/plugin-audit/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-audit", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Audit Plugin for ObjectStack — System audit log object and audit trail", "main": "dist/index.js", diff --git a/packages/plugins/plugin-auth/CHANGELOG.md b/packages/plugins/plugin-auth/CHANGELOG.md index 969f02f2e84..7dc7a0d7cf3 100644 --- a/packages/plugins/plugin-auth/CHANGELOG.md +++ b/packages/plugins/plugin-auth/CHANGELOG.md @@ -1,5 +1,62 @@ # Changelog +## 17.8.0 + +### Minor Changes + +- aa71c4d: `sys_user_permission_set` gains `permission_set`, the name of the permission set a grant holds, written beside `permission_set_id` (ADR-0131 D4) + + Clause-②: yes (widening) + + - **The column.** `permission_set` is a read-only text column, at most 100 characters, holding the `name` of the `sys_permission_set` row that `permission_set_id` points at. It is readable everywhere the grant row is readable. A grant written before this release has `NULL` here until the backfill stage rewrites it. No reader uses the column yet: the grant is still resolved from `permission_set_id`, which stays until it is dropped in a later major (ADR-0131 D10). + - **The platform writes it, on every write that carries `permission_set_id`, for every caller.** Two `@objectstack/plugin-security` engine hooks (`beforeInsert` and `beforeUpdate` on `sys_user_permission_set`) look up the set by id and store its name. A write that sends only the id, which is how the data door and the Setup forms write, gets the name filled in. + - **A name that names a different set is refused** with `400 VALIDATION_FAILED`, `invalid_value` at `permission_set`. This covers a name that disagrees with the id written beside it, or with the id already stored when only the name is written. For a non-system caller it also covers a name beside an id that names no set this caller's organization can see. A name that agrees is accepted. A cleared name (`null`) is not stored as a clear: the derived name is written back. Before this change the column did not exist, so a write naming it was refused with `400 INVALID_FIELD`. No write that was accepted before is refused now. + - **Every platform grant writer writes both columns:** the organization-admin reconcile and the platform-admin promotion in `@objectstack/plugin-security`, the self-registration grant in `@objectstack/plugin-auth`, and the RLS probe persona in `@objectstack/verify`. + - **Nothing to migrate.** No principal's grants change. To fill the column on grants written by your own code, write the set's name as `permission_set`, or leave it out and the platform fills it in. Do not write any other value there. + +### Patch Changes + +- 4935c66: The startup banner prints one line per warning class, each warning appears once, and a localhost boot no longer warns that OAuth is unencrypted. + + Clause-②: no + + - **One line per class.** Flows that declare a trigger but are not bound are grouped by trigger type and reason, with the flows listed: `⚠ 8 flows declare a 'schedule' trigger but are NOT bound — disabled by deployment policy — … (OS_AUTOMATION_SCHEDULED_WORK_ENABLED is unset or not truthy), so no time trigger arms …: flow_a, flow_b, …`. Before, the full reason (up to ~650 characters) printed once per flow. The banner now shows the reason's first sentence; `--log-level debug` still streams each flow's full reason. A real binding failure, or a missing trigger, keeps its own line, worded as before. + - **Printed once.** *Boot diagnostics* no longer repeats the automation plugin's per-flow `… is NOT bound` and shadowed-flow warnings, which the banner's `Flows:` list already shows. Its header counts them instead: `⚠ Boot diagnostics — 5 warnings logged during startup (8 more already listed above):`. Every other boot warning replays exactly as before. A boot that fails before the banner still replays all of them. + - **OAuth on loopback.** `OAuth is served UNENCRYPTED: …` is logged at `info` when the issuer's host is loopback (`localhost`, `*.localhost`, `127.0.0.0/8`, `::1`), so it is not shown at the default `warn` level. It stays `warn` on a private or link-local issuer. The sentence and the transport rule are unchanged. + - ⛔ Nothing you author changes. Which flows bind, the scheduled-work switch, the transport rule, the service-automation warning an embedded host reads, and every public key, export and parameter are unchanged. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [a7a48b7] +- Updated dependencies [b88c356] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [0db5ad5] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/service-messaging@17.8.0 + - @objectstack/rest@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/plugins/plugin-auth/package.json b/packages/plugins/plugin-auth/package.json index 262e626919a..5cc54aa5743 100644 --- a/packages/plugins/plugin-auth/package.json +++ b/packages/plugins/plugin-auth/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-auth", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Authentication & Identity Plugin for ObjectStack", "main": "dist/index.js", diff --git a/packages/plugins/plugin-dev/CHANGELOG.md b/packages/plugins/plugin-dev/CHANGELOG.md index 2c6f7782eb1..0da97f87556 100644 --- a/packages/plugins/plugin-dev/CHANGELOG.md +++ b/packages/plugins/plugin-dev/CHANGELOG.md @@ -1,5 +1,54 @@ # @objectstack/plugin-dev +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [aa71c4d] +- Updated dependencies [1920cf3] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [a7a48b7] +- Updated dependencies [b88c356] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [1fb274e] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [e67ba80] +- Updated dependencies [cdeabec] +- Updated dependencies [4935c66] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/plugin-security@17.8.0 + - @objectstack/plugin-auth@17.8.0 + - @objectstack/service-realtime@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/service-storage@17.8.0 + - @objectstack/rest@17.8.0 + - @objectstack/runtime@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/plugin-hono-server@17.8.0 + - @objectstack/driver-memory@17.8.0 + - @objectstack/service-i18n@17.8.0 + - @objectstack/account@17.8.0 + - @objectstack/setup@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/plugins/plugin-dev/package.json b/packages/plugins/plugin-dev/package.json index 4c4b503b6da..108ad2dce81 100644 --- a/packages/plugins/plugin-dev/package.json +++ b/packages/plugins/plugin-dev/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-dev", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Development Assembly Plugin for ObjectStack — wires the real platform stack for zero-config local development", "main": "dist/index.js", diff --git a/packages/plugins/plugin-email/CHANGELOG.md b/packages/plugins/plugin-email/CHANGELOG.md index 0294a2ac16e..f9647b755f5 100644 --- a/packages/plugins/plugin-email/CHANGELOG.md +++ b/packages/plugins/plugin-email/CHANGELOG.md @@ -1,5 +1,58 @@ # @objectstack/plugin-email +## 17.8.0 + +### Patch Changes + +- 56bf27a: The declared-email-template boot sweep reads the stored rows in bulk and no longer rewrites a template that has not changed + + Clause-②: no + + Every boot used to look each effective email template up on its own and rewrite its + `sys_email_template` row unconditionally: one lookup, one UPDATE and the engine's two read-backs + per template, whether or not anything had changed. Measured on `ObjectQL` over `SqlDriver` + (better-sqlite3), a steady boot over 450 unchanged templates sent 1,800 statements; it now sends 3 + reads and no write (79 templates: 316 statements, now 1). + + - The sweep reads the stored rows for the declared names in `$in` pages of 200 names. Each + `(name, locale)` takes the first row the read returns, in the order the driver gives the + per-template lookup, so a slot several organizations hold resolves to the same row as before. + - A template the bulk read did not answer (new since the last boot, a read cut short at its row + bound, or a failed read) is looked up on its own before anything is inserted, as before. + - A `managed_by: 'package'` row that already holds every column the template projects is not + rewritten. `upsertDeclaredEmailTemplate` now returns `false` for it, and + `bootstrapDeclaredEmailTemplates` counts it under `skipped`, the value both already document for + a row deliberately not written. A row with any other provenance is handled exactly as before: + admin-owned and customized rows are never written, and a legacy row with no `managed_by` is + rewritten and adopted. + + An org-scoped template edit still survives the next boot. Signatures and accepted input are unchanged. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/formula@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/plugins/plugin-email/package.json b/packages/plugins/plugin-email/package.json index 4e82ac33d6a..b5de085424c 100644 --- a/packages/plugins/plugin-email/package.json +++ b/packages/plugins/plugin-email/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-email", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Email service plugin for ObjectStack — IEmailService + transport-pluggable outbound delivery with sys_email persistence.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-hono-server/CHANGELOG.md b/packages/plugins/plugin-hono-server/CHANGELOG.md index 7f8518a259b..dd5666790e4 100644 --- a/packages/plugins/plugin-hono-server/CHANGELOG.md +++ b/packages/plugins/plugin-hono-server/CHANGELOG.md @@ -1,5 +1,46 @@ # @objectstack/plugin-hono-server +## 17.8.0 + +### Minor Changes + +- 5cfd866: feat(sharing): a share-link password can be sent in the `X-Share-Password` header whatever its characters, under a declared encoding (`X-Share-Password-Encoding: utf-8`) + + Clause-②: yes (widening) + + - **What was missing.** A browser cannot put a character above U+00FF in a request header (`Headers` throws a `TypeError` before the request leaves), and it strips leading and trailing spaces. `createLink` accepts any password, so a link whose password has a CJK character or an emoji could not be opened through the header. + - **What is now accepted.** A new companion request header, `X-Share-Password-Encoding`, declares how `X-Share-Password` is encoded. Its one value is `utf-8`, compared case-insensitively. Under it, `X-Share-Password` carries the password's UTF-8 bytes percent-encoded, as `encodeURIComponent(password)` produces them, and both public routes (`GET /api/v1/share-links/:token/resolve` and `/:token/messages`) decode it on both mounts: the sharing plugin's routes and the runtime dispatcher's `/share-links` domain. Both read the pair through one helper, `readSharePasswordHeader`, exported from `@objectstack/types` with the header-name constants. + - **Unchanged.** Without `X-Share-Password-Encoding`, `X-Share-Password` is read raw, exactly as before, so every value a client sends today resolves as it did. That includes a Latin-1 password and a raw password containing `%`; the server never percent-decodes a value nobody declared encoded. The `?password=` query parameter is still read first, and when it is present the header pair is not read. + - **What is refused.** `X-Share-Password-Encoding` naming any other value, or a password header that is not percent-encoded UTF-8 under `utf-8` (a `%` without two hex digits, octets that are not well-formed UTF-8, a character outside visible ASCII), answers `400 VALIDATION_FAILED` before the token is looked up. It is never compared raw instead. The message names the headers and the rule, never the presented value. + - **Response headers.** Both public routes now answer `Vary: X-Share-Password, X-Share-Password-Encoding`, still beside `Cache-Control: no-store`. + - **Cross-origin clients.** `X-Share-Password-Encoding` is in the default CORS preflight allow-list (`DEFAULT_CORS_ALLOW_HEADERS` in `@objectstack/plugin-hono-server`, which the `@objectstack/hono` adapter also applies). A deployment that passes its own `allowHeaders` must add `X-Share-Password-Encoding` beside `X-Share-Password` to let a cross-origin client send an encoded password. + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/observability@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/plugins/plugin-hono-server/package.json b/packages/plugins/plugin-hono-server/package.json index 0a83383a1cd..206f0e7ecdc 100644 --- a/packages/plugins/plugin-hono-server/package.json +++ b/packages/plugins/plugin-hono-server/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-hono-server", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Standard Hono Server Adapter for ObjectStack Runtime", "main": "dist/index.js", diff --git a/packages/plugins/plugin-pinyin-search/CHANGELOG.md b/packages/plugins/plugin-pinyin-search/CHANGELOG.md index c1c9143bc57..ca7e0615341 100644 --- a/packages/plugins/plugin-pinyin-search/CHANGELOG.md +++ b/packages/plugins/plugin-pinyin-search/CHANGELOG.md @@ -1,5 +1,17 @@ # @objectstack/plugin-pinyin-search +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [f85a83b] +- Updated dependencies [5cfd866] + - @objectstack/core@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/plugins/plugin-pinyin-search/package.json b/packages/plugins/plugin-pinyin-search/package.json index bea88075d0c..6c3e06616d8 100644 --- a/packages/plugins/plugin-pinyin-search/package.json +++ b/packages/plugins/plugin-pinyin-search/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-pinyin-search", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Pinyin search recall for ObjectStack — populates the hidden `__search` companion column (full pinyin + initials of the display/name field) so `$search` hits CJK names typed as pinyin. Locale-gated via OS_SEARCH_PINYIN_ENABLED (#2486).", "main": "dist/index.js", diff --git a/packages/plugins/plugin-security/CHANGELOG.md b/packages/plugins/plugin-security/CHANGELOG.md index 329009209fd..fb659686163 100644 --- a/packages/plugins/plugin-security/CHANGELOG.md +++ b/packages/plugins/plugin-security/CHANGELOG.md @@ -1,5 +1,62 @@ # @objectstack/plugin-security +## 17.8.0 + +### Minor Changes + +- aa71c4d: `sys_user_permission_set` gains `permission_set`, the name of the permission set a grant holds, written beside `permission_set_id` (ADR-0131 D4) + + Clause-②: yes (widening) + + - **The column.** `permission_set` is a read-only text column, at most 100 characters, holding the `name` of the `sys_permission_set` row that `permission_set_id` points at. It is readable everywhere the grant row is readable. A grant written before this release has `NULL` here until the backfill stage rewrites it. No reader uses the column yet: the grant is still resolved from `permission_set_id`, which stays until it is dropped in a later major (ADR-0131 D10). + - **The platform writes it, on every write that carries `permission_set_id`, for every caller.** Two `@objectstack/plugin-security` engine hooks (`beforeInsert` and `beforeUpdate` on `sys_user_permission_set`) look up the set by id and store its name. A write that sends only the id, which is how the data door and the Setup forms write, gets the name filled in. + - **A name that names a different set is refused** with `400 VALIDATION_FAILED`, `invalid_value` at `permission_set`. This covers a name that disagrees with the id written beside it, or with the id already stored when only the name is written. For a non-system caller it also covers a name beside an id that names no set this caller's organization can see. A name that agrees is accepted. A cleared name (`null`) is not stored as a clear: the derived name is written back. Before this change the column did not exist, so a write naming it was refused with `400 INVALID_FIELD`. No write that was accepted before is refused now. + - **Every platform grant writer writes both columns:** the organization-admin reconcile and the platform-admin promotion in `@objectstack/plugin-security`, the self-registration grant in `@objectstack/plugin-auth`, and the RLS probe persona in `@objectstack/verify`. + - **Nothing to migrate.** No principal's grants change. To fill the column on grants written by your own code, write the set's name as `permission_set`, or leave it out and the platform fills it in. Do not write any other value there. + +### Patch Changes + +- e67ba80: The seed ownership claim no longer fires app hooks or record-change flows + + Clause-②: no + + On a freshly seeded database, the first sign-up promotes the first user to platform admin, and `claimSeedOwnership` hands every seeded row to that admin inside the same request. That write ran under a bare system context, so every claimed row went through the full write pipeline: hooks bound from app metadata fired, record-change flows ran, approvals opened on seeded records and notifications went to the new admin. Measured on hotcrm `56d98f7e` (17.7.0, a 354-row seed), the first sign-up took about 45 s, and in that time the claim fired 1,254 app hooks, ran 8 flows, opened 2 approvals and handed 8 emails to the transport. + + The claim's write now runs with `{ isSystem: true, skipAutomations: true }`. The seed itself is end-state data written without automation, and the claim keeps that rule for the write that completes it. + + - **App hooks no longer fire for the seed ownership claim.** No hook bound from metadata (an app's `hooks`, sandboxed bodies included) runs on the claim's owner change, and no record-change flow is dispatched for it. So the claim opens no approval and sends no notification. + - **Which claims.** Every pass of the claim: the promotion pass inside the first sign-up, and the pass that runs on `app:seeded` on every boot, the first and every later one. + - **Still runs.** Hooks that plugins register in code still run, so the claim still writes one audit row per claimed record (plugin-audit) and plugin-sharing still recomputes the grants the owner change earns. Every claimed row's `owner_id` is the admin and its `updated_at` still advances, exactly as before. The per-row hook ceiling and the paged fallback for very large objects are unchanged. + - **If an app relied on it.** An app hook or flow that reacted to the claim's owner change no longer sees it, just as it never saw the seed's own writes. None of the hooks or flows in ObjectStack's own example apps reads it. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/formula@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/plugins/plugin-security/package.json b/packages/plugins/plugin-security/package.json index 501e177b5a3..83141f9cb7f 100644 --- a/packages/plugins/plugin-security/package.json +++ b/packages/plugins/plugin-security/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-security", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Security Plugin for ObjectStack — RBAC, RLS, and Field-Level Security Runtime", "main": "dist/index.js", diff --git a/packages/plugins/plugin-sharing/CHANGELOG.md b/packages/plugins/plugin-sharing/CHANGELOG.md index 0d257d82503..4aa9473fd43 100644 --- a/packages/plugins/plugin-sharing/CHANGELOG.md +++ b/packages/plugins/plugin-sharing/CHANGELOG.md @@ -1,5 +1,62 @@ # @objectstack/plugin-sharing +## 17.8.0 + +### Minor Changes + +- 5cfd866: feat(sharing): a share-link password can be sent in the `X-Share-Password` header whatever its characters, under a declared encoding (`X-Share-Password-Encoding: utf-8`) + + Clause-②: yes (widening) + + - **What was missing.** A browser cannot put a character above U+00FF in a request header (`Headers` throws a `TypeError` before the request leaves), and it strips leading and trailing spaces. `createLink` accepts any password, so a link whose password has a CJK character or an emoji could not be opened through the header. + - **What is now accepted.** A new companion request header, `X-Share-Password-Encoding`, declares how `X-Share-Password` is encoded. Its one value is `utf-8`, compared case-insensitively. Under it, `X-Share-Password` carries the password's UTF-8 bytes percent-encoded, as `encodeURIComponent(password)` produces them, and both public routes (`GET /api/v1/share-links/:token/resolve` and `/:token/messages`) decode it on both mounts: the sharing plugin's routes and the runtime dispatcher's `/share-links` domain. Both read the pair through one helper, `readSharePasswordHeader`, exported from `@objectstack/types` with the header-name constants. + - **Unchanged.** Without `X-Share-Password-Encoding`, `X-Share-Password` is read raw, exactly as before, so every value a client sends today resolves as it did. That includes a Latin-1 password and a raw password containing `%`; the server never percent-decodes a value nobody declared encoded. The `?password=` query parameter is still read first, and when it is present the header pair is not read. + - **What is refused.** `X-Share-Password-Encoding` naming any other value, or a password header that is not percent-encoded UTF-8 under `utf-8` (a `%` without two hex digits, octets that are not well-formed UTF-8, a character outside visible ASCII), answers `400 VALIDATION_FAILED` before the token is looked up. It is never compared raw instead. The message names the headers and the rule, never the presented value. + - **Response headers.** Both public routes now answer `Vary: X-Share-Password, X-Share-Password-Encoding`, still beside `Cache-Control: no-store`. + - **Cross-origin clients.** `X-Share-Password-Encoding` is in the default CORS preflight allow-list (`DEFAULT_CORS_ALLOW_HEADERS` in `@objectstack/plugin-hono-server`, which the `@objectstack/hono` adapter also applies). A deployment that passes its own `allowHeaders` must add `X-Share-Password-Encoding` beside `X-Share-Password` to let a cross-origin client send an encoded password. + +### Patch Changes + +- 1c563af: Setup → Record Shares opens on every share, not on the shares granted to the administrator. Before this, the entry named no view, and `sys_record_share` declared the caller-scoped "Granted to Me" view (`recipient_id = {current_user_id}`) first. + + Clause-②: no + + - `sys_record_share` now declares its unscoped "All" view (`all_shares`) first. "Granted to Me" and "Granted by Me" follow it, still as tabs. No view is added, removed or changed. + - The Setup entry `nav_record_shares` now names `all_shares` with `viewName`, so it does not depend on the declared order. + - The generated translation bundles follow the new view order. No translated text changed. + - ⛔ No schema, parse, export or accept-set change. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/formula@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/plugins/plugin-sharing/package.json b/packages/plugins/plugin-sharing/package.json index 5c08c67e3a3..91583fc373b 100644 --- a/packages/plugins/plugin-sharing/package.json +++ b/packages/plugins/plugin-sharing/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-sharing", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Record-level sharing for ObjectStack — sys_record_share + middleware that enforces sharingModel + ISharingService.", "main": "dist/index.js", diff --git a/packages/plugins/plugin-webhooks/CHANGELOG.md b/packages/plugins/plugin-webhooks/CHANGELOG.md index e7927df8dba..580b4f373f4 100644 --- a/packages/plugins/plugin-webhooks/CHANGELOG.md +++ b/packages/plugins/plugin-webhooks/CHANGELOG.md @@ -1,5 +1,38 @@ # @objectstack/plugin-webhooks +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [a7a48b7] +- Updated dependencies [b88c356] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [0db5ad5] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/service-messaging@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/plugins/plugin-webhooks/package.json b/packages/plugins/plugin-webhooks/package.json index 33098b5b40c..5eff7643ad9 100644 --- a/packages/plugins/plugin-webhooks/package.json +++ b/packages/plugins/plugin-webhooks/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/plugin-webhooks", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Persistent, cluster-aware webhook dispatcher. Durable outbox + per-partition cluster.lock for exactly-once-ish delivery across nodes. See content/docs/concepts/webhook-delivery.mdx.", "type": "module", diff --git a/packages/qa/dogfood/CHANGELOG.md b/packages/qa/dogfood/CHANGELOG.md index 5cfd28f3cbf..47051465f3e 100644 --- a/packages/qa/dogfood/CHANGELOG.md +++ b/packages/qa/dogfood/CHANGELOG.md @@ -1,5 +1,72 @@ # @objectstack/dogfood +## 0.0.48 + +### Patch Changes + +- Updated dependencies [aa71c4d] +- Updated dependencies [1920cf3] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [a7a48b7] +- Updated dependencies [b88c356] +- Updated dependencies [8a399b2] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [f0022c4] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [0db5ad5] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [56bf27a] +- Updated dependencies [e67ba80] +- Updated dependencies [cdeabec] +- Updated dependencies [4935c66] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/plugin-security@17.8.0 + - @objectstack/plugin-auth@17.8.0 + - @objectstack/verify@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/service-storage@17.8.0 + - @objectstack/service-messaging@17.8.0 + - @objectstack/service-datasource@17.8.0 + - @objectstack/plugin-sharing@17.8.0 + - @objectstack/plugin-approvals@17.8.0 + - @objectstack/service-analytics@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/plugin-email@17.8.0 + - @objectstack/example-showcase@0.3.22 + - @objectstack/connector-mcp@17.8.0 + - @objectstack/connector-openapi@17.8.0 + - @objectstack/connector-rest@17.8.0 + - @objectstack/mcp@17.8.0 + - @objectstack/metadata@17.8.0 + - @objectstack/plugin-audit@17.8.0 + - @objectstack/plugin-pinyin-search@17.8.0 + - @objectstack/plugin-webhooks@17.8.0 + - @objectstack/trigger-record-change@17.8.0 + - @objectstack/trigger-schedule@17.8.0 + - @objectstack/example-crm@4.0.100 + - @objectstack/example-multi-package@0.0.7 + - @objectstack/formula@17.8.0 + ## 0.0.47 ### Patch Changes diff --git a/packages/qa/dogfood/package.json b/packages/qa/dogfood/package.json index aa767ed7377..e39bc4834d6 100644 --- a/packages/qa/dogfood/package.json +++ b/packages/qa/dogfood/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/dogfood", - "version": "0.0.47", + "version": "0.0.48", "private": true, "license": "Apache-2.0", "description": "Dogfood regression gate — hand-written golden tests that boot real example apps through @objectstack/verify's in-process HTTP stack, pinning historical runtime regressions (#2018 timezone bucketing, #1994 cross-owner RLS, #2004 field fidelity) that static checks miss.", diff --git a/packages/qa/downstream-contract/CHANGELOG.md b/packages/qa/downstream-contract/CHANGELOG.md index 3373271fb8b..3aa60e6e518 100644 --- a/packages/qa/downstream-contract/CHANGELOG.md +++ b/packages/qa/downstream-contract/CHANGELOG.md @@ -1,5 +1,28 @@ # @objectstack/downstream-contract +## 0.0.46 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + ## 0.0.45 ### Patch Changes diff --git a/packages/qa/downstream-contract/package.json b/packages/qa/downstream-contract/package.json index 8bfae3ba548..a10a3806107 100644 --- a/packages/qa/downstream-contract/package.json +++ b/packages/qa/downstream-contract/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/downstream-contract", - "version": "0.0.45", + "version": "0.0.46", "description": "Frozen third-party consumer fixture — a backward-compatibility gate for @objectstack/spec. Authored the way an external project on a published release authors metadata; if a spec change breaks it, that change is breaking (#2035).", "license": "Apache-2.0", "private": true, diff --git a/packages/qa/http-conformance/CHANGELOG.md b/packages/qa/http-conformance/CHANGELOG.md index f0cb683aa87..0d96013f11d 100644 --- a/packages/qa/http-conformance/CHANGELOG.md +++ b/packages/qa/http-conformance/CHANGELOG.md @@ -1,5 +1,12 @@ # @objectstack/http-conformance +## 0.1.8 + +### Patch Changes + +- Updated dependencies [c28f317] + - @objectstack/core@17.8.0 + ## 0.1.7 ### Patch Changes diff --git a/packages/qa/http-conformance/package.json b/packages/qa/http-conformance/package.json index 9ec69ddd3d2..17a390bd2c3 100644 --- a/packages/qa/http-conformance/package.json +++ b/packages/qa/http-conformance/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/http-conformance", - "version": "0.1.7", + "version": "0.1.8", "private": true, "license": "Apache-2.0", "description": "HTTP transport-port conformance gate (ADR-0076 D11/OQ#10, #2462) — a zero-dependency node:http reference implementation of IHttpServer plus a cross-adapter suite that boots the dispatcher bridge and REST generator on it AND on plugin-hono-server, pinning that the port stays free of framework-isms. Not published; validation instrument, not a product server.", diff --git a/packages/rest/CHANGELOG.md b/packages/rest/CHANGELOG.md index b83086a8a1e..dcb6df48380 100644 --- a/packages/rest/CHANGELOG.md +++ b/packages/rest/CHANGELOG.md @@ -1,5 +1,45 @@ # @objectstack/rest +## 17.8.0 + +### Patch Changes + +- 1fb274e: `GET /api/v1/meta/datasource/:name/published` serves a code-defined datasource's code definition while a stored row under its name still exists + + Clause-②: no + + - For a datasource name the host registers from code (one an installed package declares in `*.datasource.ts`, or the host's `default`), the published door now serves the layered read's `effective` layer, which is the code definition. Before this change it served the leftover stored `sys_metadata` row, with that row's label, `origin` and connection settings, while `GET /api/v1/meta/datasource/:name`, the `/meta/datasource` list and `/layers` all served the code definition. The door now asks the protocol's `declinesStoredRow`, the one decision those reads make, in place of `isShippedFlowName`. A shipped flow name is answered as before. + - Unchanged: a runtime datasource's stored row is still what the door serves, and so is every stored row of every other type. A protocol that does not provide `declinesStoredRow` still gets the stored row. The row itself stays at rest, `/layers` still reports it in `overlay`, and `DELETE /api/v1/meta/datasource/:name` still removes it as the repair. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/service-package@17.8.0 + - @objectstack/observability@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/rest/package.json b/packages/rest/package.json index 9e4f9c9f914..fa7f48be2de 100644 --- a/packages/rest/package.json +++ b/packages/rest/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/rest", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "ObjectStack REST API Server - automatic REST endpoint generation from protocol", "type": "module", diff --git a/packages/runtime/CHANGELOG.md b/packages/runtime/CHANGELOG.md index 7a7ca6cd088..f4aff572be5 100644 --- a/packages/runtime/CHANGELOG.md +++ b/packages/runtime/CHANGELOG.md @@ -1,5 +1,94 @@ # @objectstack/runtime +## 17.8.0 + +### Minor Changes + +- 5cfd866: feat(sharing): a share-link password can be sent in the `X-Share-Password` header whatever its characters, under a declared encoding (`X-Share-Password-Encoding: utf-8`) + + Clause-②: yes (widening) + + - **What was missing.** A browser cannot put a character above U+00FF in a request header (`Headers` throws a `TypeError` before the request leaves), and it strips leading and trailing spaces. `createLink` accepts any password, so a link whose password has a CJK character or an emoji could not be opened through the header. + - **What is now accepted.** A new companion request header, `X-Share-Password-Encoding`, declares how `X-Share-Password` is encoded. Its one value is `utf-8`, compared case-insensitively. Under it, `X-Share-Password` carries the password's UTF-8 bytes percent-encoded, as `encodeURIComponent(password)` produces them, and both public routes (`GET /api/v1/share-links/:token/resolve` and `/:token/messages`) decode it on both mounts: the sharing plugin's routes and the runtime dispatcher's `/share-links` domain. Both read the pair through one helper, `readSharePasswordHeader`, exported from `@objectstack/types` with the header-name constants. + - **Unchanged.** Without `X-Share-Password-Encoding`, `X-Share-Password` is read raw, exactly as before, so every value a client sends today resolves as it did. That includes a Latin-1 password and a raw password containing `%`; the server never percent-decodes a value nobody declared encoded. The `?password=` query parameter is still read first, and when it is present the header pair is not read. + - **What is refused.** `X-Share-Password-Encoding` naming any other value, or a password header that is not percent-encoded UTF-8 under `utf-8` (a `%` without two hex digits, octets that are not well-formed UTF-8, a character outside visible ASCII), answers `400 VALIDATION_FAILED` before the token is looked up. It is never compared raw instead. The message names the headers and the rule, never the presented value. + - **Response headers.** Both public routes now answer `Vary: X-Share-Password, X-Share-Password-Encoding`, still beside `Cache-Control: no-store`. + - **Cross-origin clients.** `X-Share-Password-Encoding` is in the default CORS preflight allow-list (`DEFAULT_CORS_ALLOW_HEADERS` in `@objectstack/plugin-hono-server`, which the `@objectstack/hono` adapter also applies). A deployment that passes its own `allowHeaders` must add `X-Share-Password-Encoding` beside `X-Share-Password` to let a cross-origin client send an encoded password. + +### Patch Changes + +- 1fb274e: The runtime dispatcher's `GET /meta/datasource/:name/published` serves a code-defined datasource's code definition while a stored row under its name still exists + + Clause-②: no + + - This is the dispatcher twin of the `@objectstack/rest` published door, and it now answers the same way. For a datasource name the host registers from code (one an installed package declares in `*.datasource.ts`, or the host's `default`), the door serves the layered read's `effective` layer, which is the code definition, instead of the leftover stored row. It asks the protocol's `declinesStoredRow` in place of `isShippedFlowName`. A shipped flow name is answered as before. + - Unchanged: a runtime datasource's stored row, and every stored row of every other type, is served as before. So is every row when the protocol does not provide `declinesStoredRow`. +- a543e24: `POST /packages/:id/revert` reverts a Studio-authored package instead of answering 404 "No metadata items found" + + Clause-②: no + + - The door asked only the metadata service, whose in-memory registry never holds a Studio package's stored rows. So a package with published items and a pending draft answered `404 RESOURCE_NOT_FOUND`, and the draft stayed. + - The door now asks the protocol's `revertStoredPackage` first, with the caller's active organization. A package with stored rows answers `200 { success: true }` with its drafts removed, or `409 RESOURCE_CONFLICT` when it has never been published. + - A package with no stored row is answered by the metadata service's `revertPackage`, exactly as before. That covers a code-shipped package and an unknown id (`404`). It is also what happens when the protocol does not provide `revertStoredPackage`. + - The request and the response shape are unchanged. +- Updated dependencies [c28f317] +- Updated dependencies [aa71c4d] +- Updated dependencies [1920cf3] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [b88c356] +- Updated dependencies [1abfc58] +- Updated dependencies [8a399b2] +- Updated dependencies [db87a02] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [6befe19] +- Updated dependencies [8caa131] +- Updated dependencies [1fb274e] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [f85a83b] +- Updated dependencies [2015c54] +- Updated dependencies [f2a45db] +- Updated dependencies [3d91885] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [ae97841] +- Updated dependencies [e67ba80] +- Updated dependencies [cdeabec] +- Updated dependencies [4935c66] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/plugin-security@17.8.0 + - @objectstack/plugin-auth@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/metadata-protocol@17.8.0 + - @objectstack/service-datasource@17.8.0 + - @objectstack/rest@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/driver-memory@17.8.0 + - @objectstack/driver-sql@17.8.0 + - @objectstack/driver-sqlite-wasm@17.8.0 + - @objectstack/driver-turso@17.8.0 + - @objectstack/metadata@17.8.0 + - @objectstack/service-cluster@17.8.0 + - @objectstack/service-i18n@17.8.0 + - @objectstack/formula@17.8.0 + - @objectstack/observability@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/runtime/package.json b/packages/runtime/package.json index c8df80222aa..628714d1f12 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/runtime", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "ObjectStack Core Runtime & Query Engine", "type": "module", diff --git a/packages/sdui-parser/CHANGELOG.md b/packages/sdui-parser/CHANGELOG.md index 9a5a5eef7cf..4d02a6aa8a3 100644 --- a/packages/sdui-parser/CHANGELOG.md +++ b/packages/sdui-parser/CHANGELOG.md @@ -1,5 +1,9 @@ # @objectstack/sdui-parser +## 17.8.0 + +No changes in this release. + ## 17.7.0 ### Minor Changes diff --git a/packages/sdui-parser/package.json b/packages/sdui-parser/package.json index a5cd02b541d..f6354c9de1b 100644 --- a/packages/sdui-parser/package.json +++ b/packages/sdui-parser/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/sdui-parser", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "ObjectStack constrained JSX-source → SDUI SchemaNode tree compiler (parse, never execute). Isomorphic, zero React. ADR-0080.", "main": "dist/index.js", diff --git a/packages/services/service-analytics/CHANGELOG.md b/packages/services/service-analytics/CHANGELOG.md index eefb4a159d3..40bd1471f20 100644 --- a/packages/services/service-analytics/CHANGELOG.md +++ b/packages/services/service-analytics/CHANGELOG.md @@ -1,5 +1,44 @@ # Changelog — @objectstack/service-analytics +## 17.8.0 + +### Minor Changes + +- c565813: feat(spec,analytics): a dataset answer's measure column states its aggregate, labelled or not (`fields[].aggregate`) + + Clause-②: yes (widening) + + - **What a renderer can now read.** Each measure column of a dataset answer (`POST /analytics/dataset/query`) carries `fields[].aggregate`: the aggregate its dataset measure declares, in the closed `AggregationFunction` vocabulary (`count`, `sum`, `avg`, `min`, `max`, `count_distinct`). It is there whether or not the author gave the measure a `label`. So a chart can tell a count from a sum, for example to draw whole-number axis ticks for a count instead of 0.75 / 1.5 / 2.25. + - **What was missing.** The only aggregate on the wire was `builtinAggregate`, and it is present only when the measure has no `label`. A labelled measure, such as a `count` named "Tasks", reached the wire as `{ name, type: 'number', label }`, with nothing to say what kind of number it was. + - **Where it is set.** `AnalyticsService` writes it in the one step that describes a dataset answer's columns from the dataset's own measures. That step runs for both the live query and the draft-data preview, so the two answers agree. A measure's `__compare` column carries the same aggregate. + - **Where it is absent.** Dimension columns. Derived measures, which combine other measures and have no single aggregate (a stray `aggregate` written beside `derived` is ignored when the dataset compiles, so it is not stated here either). And a cube query answer (`POST /analytics/query`), which does not run through the dataset column step. + - **Unchanged.** `builtinAggregate` keeps its meaning: present only on a label-less measure column, to mark a header that is the server's default. No authoring key is added; `aggregate` is a response member only. `AnalyticsResultResponseSchema` and the `AnalyticsResult` contract declare the member, and the REST route relays it as it does every other column key. + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/services/service-analytics/package.json b/packages/services/service-analytics/package.json index 1373265dd30..3981b1e3b89 100644 --- a/packages/services/service-analytics/package.json +++ b/packages/services/service-analytics/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-analytics", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Analytics Service for ObjectStack — implements IAnalyticsService with multi-driver strategy pattern (NativeSQL, ObjectQL, InMemory)", "type": "module", diff --git a/packages/services/service-automation/CHANGELOG.md b/packages/services/service-automation/CHANGELOG.md index 8967a751769..2af722b40cf 100644 --- a/packages/services/service-automation/CHANGELOG.md +++ b/packages/services/service-automation/CHANGELOG.md @@ -1,5 +1,121 @@ # @objectstack/service-automation +## 17.8.0 + +### Minor Changes + +- 1920cf3: feat(platform-objects,service-automation,service-realtime)!: seven deployment-level platform tables lose their injected organization column, and reading them needs `manage_platform_settings` (ADR-0131 D7) + + Clause-②: no (narrowing) + + + + **BREAKING**, shipped as `minor` under the repo's launch-window convention for breaking changes (Changesets pre mode is not on yet). + + `sys_job`, `sys_job_run`, `sys_job_queue`, `sys_flow_dispatch`, `sys_migration`, `sys_migration_journal` and `sys_presence` hold deployment-level state. No writer attributes a row of any of them to an organization: every write is a system-context write whose row names none, and nothing writes `sys_presence` through ObjectQL at all. So the injected `organization_id` column only ever held NULL. ADR-0131 D7 takes it off: each object now declares `systemFields: { tenant: false }`. + + With no column there is no tenant wall, so these tables are governed by object permission. Each also declares `requiredPermissions: ['manage_platform_settings']`. Without that gate, a walled deployment's `organization_admin`, whose grant carries the superuser bits on every object, would read every other organization's job errors, queued payloads, dispatch keys and migration traces. + + **What moves for consumers.** + + - **The column.** `organization_id` is no longer a field of these seven objects. A filter, list-view column, report grouping, formula or seed key naming it on one of them is now an unknown field. Delete the reference: no organization owns a row of these tables. + - **Who reads, on a walled posture** (`group` or `isolated`). Before: the wall compared the NULL column to the caller's organization, so every reader got zero rows, platform administrators included (unless the deployment declared the table platform-global, which stood the wall down). Now: a principal holding `manage_platform_settings` (platform administrators hold it) lists every row; anyone else is refused `403 PERMISSION_DENIED`. + - **Who reads, on the `single` posture.** Before: any principal with a read grant on the object read every row, an organization administrator included. Now: only a principal holding `manage_platform_settings` reads; an organization administrator who is not a platform administrator is refused `403 PERMISSION_DENIED`. Grant the capability to an operator who needs these tables. + + **Unchanged.** Every platform writer and reader of these tables uses a system context, which no capability gate applies to, so job scheduling, the queue, flow dispatch, migration flags and the migration journal behave as before. The physical unique indexes are unchanged: none of these objects declares an organization-scoped one. + + **Existing databases.** Schema sync only adds, so the physical `organization_id` column stays on each existing table (with its index, where the deployment indexed it), and the boot drift report names it orphaned. By the writer census it holds only NULL, so dropping it loses nothing: `os migrate apply --allow-destructive` drops it, the remedy the drift report names. + +### Patch Changes + +- d4680d2: feat(spec)!: `NotifyConfigSchema.title` and `NotifyConfigSchema.message` are template slots: each takes a bare string or a `{ dialect: 'template', source }` envelope (the `tmpl` helper), as the expression dialect table already listed notification subjects and bodies among the `template` slots, and a blank bare string is now refused there + + Clause-②: yes (narrowing) + + + + **BREAKING** accept-set narrowing at two authorable keys (`automation/NotifyConfig:title`, `automation/NotifyConfig:message`), shipped as `minor` under the repo's launch-window convention for breaking changes. It is the grade `TemplateExpressionInputSchema`'s blank-string rule shipped with when it reached the first twelve typed keys. + + - **What widens.** Both keys are typed with `TemplateExpressionInputSchema`, the input every other `template` slot uses. Before, both were `z.string()`, so a notify node written with `` tmpl`…` `` passed `defineFlow` and registration and then failed every run at the execute-time contract parse (`expected string, received object`). It now parses and runs. + - **What narrows.** A blank bare string (`''` or whitespace-only) at either key is newly refused, by the shared template input's non-blank rule (`invalid_union`, with the `TYPED_EXPRESSION_SOURCE_REQUIRED.template` sentence). Before, every blank value parsed: + - `title: ''` then failed every run at the executor's guard ("notify: title is required"), so it fails either way, now earlier; + - a whitespace-only `title` passed that guard and was delivered as the notification title, and it is now refused; + - `message: ''` or a whitespace-only `message` was delivered as an empty or blank body, and it is now refused. + + The fix is to write the text, or to delete the key (`message` is optional). + - **Parse output.** `NotifyConfigSchema.parse(...).title` and `.message` go from `string` to `{ dialect: 'template', source }`, for both spellings, because the parse normalizes a bare string to that envelope. The exported `NotifyConfigParsed` type changes with them. Code that reads parse output reads `.source`. The `notify` executor, the one reader in this repo, now does, so both spellings of one text deliver the same `payload.title` and `payload.body`, and a bare string renders exactly what it rendered before. + - **Still refused, with a new sentence.** A value that is neither a string nor a template envelope (a number, an array, a `cel` envelope) was refused before (`invalid_type`). It is refused now as `invalid_union`, with the `TYPED_EXPRESSION_DIALECT_ONLY.template` sentence. + - **New, notify-only.** A template envelope on either key must carry a non-blank `source`. The executor renders `source` and has nothing to render from `ast` alone, so such an envelope is refused at the key instead of failing every run (`title`) or sending an empty body (`message`). An envelope never parsed at these keys before, so this refuses nothing that used to parse. + - **Placeholder spelling.** These two slots read the flow's single-brace `{token}` (`{record.name}`). A `{{var}}` is not a placeholder here: the inner `{var}` resolves and the outer braces stay in the text, for a bare string and an envelope alike. The `.describe()` on both keys now says so, and no longer says the text is "sent verbatim". + - `@objectstack/service-automation`: the `notify` executor reads `source` from the two template slots, and the descriptor's `title` / `message` descriptions state the `{token}` interpolation in place of "sent verbatim". +- 51290bc: Form help and refusals an author reads no longer carry service-interface names, ruling dates or another product's ids + + Clause-②: no + + Wording only: no schema, key, type, export or error-code change. + + - The notify node's Template help (the `NotifyConfigSchema.template` describe and the Studio + inspector's copy in `@objectstack/service-automation`) names the deployment's default locale in + product words instead of `II18nService.getDefaultLocale()`, and drops its ruling date. + - `MANIFEST_ID_EXAMPLES` is now `com.acme.crm` and `org.example.help-desk` (was `com.steedos.crm` + and `org.apache.superset`). The package-id refusal opens with the headline + "Invalid package id 'VALUE'." and names the key in the sentence after it, so the headline alone + carries no JSON path; the rule, the examples and the suggestion follow unchanged in substance. A + caller that matched the old "on KEY. Expected reverse-domain notation" wording matches the + headline, or compares against `manifestIdRefusal()` by reference, instead. + - Ruling dates leave the describes Studio renders as form help: field `required` and `multiple`, + form-view field and section `visibleWhen`, section `collapsible` / `collapsed`, the redirect + arm's `submitBehavior.url`, and page `kind` / `source` (the ADR citations stay). They also + leave the refusals for padded grouping field names, `submitBehavior.url`, `features.*` in a + form-view predicate, and the four filter comparand refusals (null ordering comparand, + `{ $field }` in a list position, null list member, blank `$between` bound). Each sentence still + states the rule, why it exists and the repair. + - The email-template form's Identity section help says how senders address a template instead of + naming `IEmailService.sendTemplate`, in all four shipped locales. + - The action `description` help no longer ends in a dangling dash left behind by an earlier + strip: "(one dialog, not two —)" now reads "(one dialog, not two)". +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [b88c356] +- Updated dependencies [1abfc58] +- Updated dependencies [db87a02] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [6befe19] +- Updated dependencies [8caa131] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [2015c54] +- Updated dependencies [f2a45db] +- Updated dependencies [3d91885] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [ae97841] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-protocol@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/formula@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/services/service-automation/package.json b/packages/services/service-automation/package.json index 0f4f6b38570..24ba014a597 100644 --- a/packages/services/service-automation/package.json +++ b/packages/services/service-automation/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-automation", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Automation Service for ObjectStack — implements IAutomationService with plugin-based DAG flow execution engine", "type": "module", diff --git a/packages/services/service-cache/CHANGELOG.md b/packages/services/service-cache/CHANGELOG.md index cbe8f348734..6a2d9265965 100644 --- a/packages/services/service-cache/CHANGELOG.md +++ b/packages/services/service-cache/CHANGELOG.md @@ -1,5 +1,31 @@ # @objectstack/service-cache +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/observability@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-cache/package.json b/packages/services/service-cache/package.json index eba700989e3..8d9f4c1329f 100644 --- a/packages/services/service-cache/package.json +++ b/packages/services/service-cache/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cache", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Cache Service for ObjectStack — implements ICacheService with in-memory and Redis adapters", "type": "module", diff --git a/packages/services/service-cluster-redis/CHANGELOG.md b/packages/services/service-cluster-redis/CHANGELOG.md index 98e248f13e3..bc884cad17b 100644 --- a/packages/services/service-cluster-redis/CHANGELOG.md +++ b/packages/services/service-cluster-redis/CHANGELOG.md @@ -1,5 +1,29 @@ # @objectstack/service-cluster-redis +## 17.8.0 + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + - @objectstack/service-cluster@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-cluster-redis/package.json b/packages/services/service-cluster-redis/package.json index dfbe7126f7e..97f50e8f7d9 100644 --- a/packages/services/service-cluster-redis/package.json +++ b/packages/services/service-cluster-redis/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cluster-redis", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Redis cluster driver for ObjectStack — implements IPubSub/ILock/IKV/ICounter against Redis using ioredis.", "type": "module", diff --git a/packages/services/service-cluster/CHANGELOG.md b/packages/services/service-cluster/CHANGELOG.md index c23179a26b3..0c08509d05e 100644 --- a/packages/services/service-cluster/CHANGELOG.md +++ b/packages/services/service-cluster/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/service-cluster +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-cluster/package.json b/packages/services/service-cluster/package.json index 3a463b604b5..856a8a3a0ad 100644 --- a/packages/services/service-cluster/package.json +++ b/packages/services/service-cluster/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-cluster", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Cluster Service for ObjectStack — pluggable PubSub/Lock/KV/Counter primitives. Memory driver included; postgres/redis drivers ship separately.", "type": "module", diff --git a/packages/services/service-datasource/CHANGELOG.md b/packages/services/service-datasource/CHANGELOG.md index 6a5b3285d63..9dbe4c339e7 100644 --- a/packages/services/service-datasource/CHANGELOG.md +++ b/packages/services/service-datasource/CHANGELOG.md @@ -1,5 +1,51 @@ # @objectstack/service-external-datasource +## 17.8.0 + +### Patch Changes + +- 8a399b2: The datasource admin door and the metadata door agree on a runtime datasource: a datasource saved through `/api/v1/meta/datasource/:name` is listed and editable through `/api/v1/datasources` in the same boot and after a restart, and one created through the admin door can be edited and removed through the metadata door (#21923) + + Clause-②: no + + `DatasourceSchema.origin` publishes `runtime` as "created via the Studio wizard, persisted in the runtime metadata store, environment-scoped, editable", and says `origin` is never accepted from client input. Three things broke that for a datasource the metadata door wrote, or one the admin door wrote: + + - **The admin door read `origin` from the record.** It served `origin ?? 'code'`, so after a restart a datasource saved through the metadata door (whose body carries no `origin`, or asserts `origin: 'code'`) was served as code-defined, and `PATCH /api/v1/datasources/:name` answered `400 DATASOURCE_ADMIN_ERROR` ("… is code-defined and cannot be edited at runtime."). It now serves `code` only for a name the host registers from code (the host's code-datasource set, the one the boot restore and the metadata door's refusal read), and `runtime` for every other name, whatever the record says. Boot pool rehydration follows the served origin, so such a datasource also gets its live pool after a restart. + - **A metadata-door write never reached the admin door until a restart.** The metadata door persisted the row but never registered it where the admin door lists, so in the same boot `GET /api/v1/datasources` omitted it and `PATCH` answered "not found". The datasource-admin plugin now registers the protocol's awaited `datasource` mutation projector: after a metadata-door save, publish, revert, rollback or delete, the admin door's registry follows the stored row and the live pool converges on it (opened on a create, rebuilt on a connectivity change, evicted on a delete) before the metadata door answers. A write under a name the host defines in code changes nothing here, so the metadata door's `DELETE` of a leftover row under such a name still leaves the code definition served. + - **An admin-created row could not be edited or removed through the metadata door.** The admin door stored its `sys_metadata` row with no `checksum`, the column the metadata door's optimistic lock compares, so `PUT` and `DELETE /api/v1/meta/datasource/:name` answered `409 METADATA_CONFLICT` for every admin-created datasource. The admin door now stamps the checksum the metadata door's repository stamps (`hashSpec` from `@objectstack/metadata-core`, which becomes a runtime dependency of this package). A row stored before this release has no checksum until the admin door next writes it: editing that datasource through the admin door once makes it editable through the metadata door. + + Cluster convergence (a peer replica's signal after an admin-door write) decides "code" from the same set, so a stored row under a code-defined name never opens a pool there, and every other stored row is pooled as runtime. + + **Unchanged.** A code-defined datasource stays read-only through both doors: the admin door's `PATCH` and `DELETE` still answer `400 DATASOURCE_ADMIN_ERROR`, and the metadata door's still answer `403 NOT_OVERRIDABLE`. A host that composes no code-datasource producer (neither `AppPlugin` nor `DefaultDatasourcePlugin`) registers no set, and the admin door then serves every datasource as runtime, as its boot restore already treats every stored row. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/driver-memory@17.8.0 + - @objectstack/driver-mongodb@17.8.0 + - @objectstack/driver-sql@17.8.0 + - @objectstack/driver-sqlite-wasm@17.8.0 + - @objectstack/driver-turso@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/services/service-datasource/package.json b/packages/services/service-datasource/package.json index 9b21e553722..383e684084a 100644 --- a/packages/services/service-datasource/package.json +++ b/packages/services/service-datasource/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-datasource", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "The datasource service (ADR-0015): external-table federation (introspect/draft/import/validate) + runtime UI datasource lifecycle (list/test/create/update/remove + REST routes). Open-source mechanism; the tier line falls on which ICryptoProvider / driver factory a host injects.", "type": "module", diff --git a/packages/services/service-i18n/CHANGELOG.md b/packages/services/service-i18n/CHANGELOG.md index d4c57f2d715..b2e69a0970b 100644 --- a/packages/services/service-i18n/CHANGELOG.md +++ b/packages/services/service-i18n/CHANGELOG.md @@ -1,5 +1,32 @@ # @objectstack/service-i18n +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-i18n/package.json b/packages/services/service-i18n/package.json index 6e60680c01d..f305c5eed4c 100644 --- a/packages/services/service-i18n/package.json +++ b/packages/services/service-i18n/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-i18n", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "I18n Service for ObjectStack — implements II18nService with file-based locale loading", "type": "module", diff --git a/packages/services/service-job/CHANGELOG.md b/packages/services/service-job/CHANGELOG.md index 078e408e492..56329b1c43b 100644 --- a/packages/services/service-job/CHANGELOG.md +++ b/packages/services/service-job/CHANGELOG.md @@ -1,5 +1,34 @@ # @objectstack/service-job +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-job/package.json b/packages/services/service-job/package.json index bd280aea479..0a3f8e93ced 100644 --- a/packages/services/service-job/package.json +++ b/packages/services/service-job/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-job", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Job Service for ObjectStack — implements IJobService with setInterval and cron scheduling", "type": "module", diff --git a/packages/services/service-knowledge/CHANGELOG.md b/packages/services/service-knowledge/CHANGELOG.md index b26f06d15e6..64669a22081 100644 --- a/packages/services/service-knowledge/CHANGELOG.md +++ b/packages/services/service-knowledge/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/service-knowledge +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/services/service-knowledge/package.json b/packages/services/service-knowledge/package.json index c86802c013c..2393ad8c1ee 100644 --- a/packages/services/service-knowledge/package.json +++ b/packages/services/service-knowledge/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-knowledge", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Knowledge Service for ObjectStack — orchestrator implementing IKnowledgeService over pluggable IKnowledgeAdapter backends (RAGFlow, LlamaIndex, Dify, in-memory).", "type": "module", diff --git a/packages/services/service-messaging/CHANGELOG.md b/packages/services/service-messaging/CHANGELOG.md index 07a403f9d45..5a1ce4da5f3 100644 --- a/packages/services/service-messaging/CHANGELOG.md +++ b/packages/services/service-messaging/CHANGELOG.md @@ -1,5 +1,71 @@ # @objectstack/service-messaging +## 17.8.0 + +### Minor Changes + +- 0db5ad5: fix(service-messaging)!: mark-read writes a read receipt only for a notification delivered to that user + + Clause-②: no (narrowing) + + + + **BREAKING** (an accept-set narrowing), shipped as `minor` under the launch-window convention: `MessagingService.markRead` — the method behind the notifications mark-read door, and behind `markReadAsCaller` — writes a `read` receipt only for a notification that was delivered to that user. A read receipt belongs to a recipient (ADR-0030 keys it by recipient). + + - **Delivered** means a receipt keyed on that user already exists (it is flipped to `read` in place, as before), or the user's inbox holds a message for that notification. The inbox message is enough on its own, because the inbox channel's `delivered` receipt is best-effort. + - **Any other id** writes no receipt, is not counted in `readCount`, and its notification's organization is not read. The response shape `{ success, readCount }` is unchanged. + - **`markAllRead`** is unchanged: every id it sweeps comes from the user's own inbox. + + What changes for you: nothing in what you write. A `readCount` lower than the number of ids sent means some of them were not notifications delivered to that user, and nothing was written for those. + +### Patch Changes + +- a7a48b7: The storage store's by-id methods and the HTTP outbox's `redeliver` now pass the explicit system opt-in (`{ isSystem: true }`) on their data-engine calls. Until now they reached the engine with no principal and no opt-in, and the security middleware let that through only because of its principal-less hand-off. + + Clause-②: no + + - **service-storage.** `StorageMetadataStore.getFile`, `updateFile`, `deleteFile`, `getSession`, `updateSession` and `deleteSession` take the opt-in inside the store. Access stays by id, and the reads stay unscoped by organization, as before. On update and delete the acting organization still reaches the driver beside the opt-in, so a row stamped for another organization is still out of reach of these doors. The doors keep the authorization they already ran. + - **service-storage, the update payload.** `updateFile` and `updateSession` now send the caller's patch alone, where they used to send the whole row read back merged with it. The engine's read-only strip, which does not run for a system write, used to take `organization_id` and the four audit columns out of that row; now the store never sends them. The stored row is the same as before, and a column another writer changed between the read and the write is no longer reverted by it. + - **service-messaging.** `SqlHttpOutbox.redeliver` takes the opt-in on both of its reads and on its reset write. The caller's `tenantId` stays on every call as the driver-level scope, so a delivery in another organization is still not found. The reset write states `bypassTenantAudit: false`, so a redelivery from a caller with no organization is still reported by the driver's tenant audit. + - None of the gates the security middleware runs before its hand-off applies to these calls. ⛔ No new export on either package entry, and no new elevation API. +- b88c356: The remaining platform producers in these four packages now pass the explicit system opt-in (`{ isSystem: true }`) on their data-engine calls. Until now they reached the engine with no principal and no opt-in, and the security middleware let that through only because of its principal-less hand-off. + + Clause-②: no + + - **service-messaging, the inbox read state.** `listInbox` (and its unread total), the receipt read behind it, and mark-read / mark-all-read take the opt-in inside the service. Their scope is unchanged: every read of a user's rows is keyed on the user id the door derived from the session, the receipt a mark-read inserts is stamped with it, and the receipt it updates is one a user-keyed read returned. + - **service-messaging, `owner_of:` audiences.** The record read takes the opt-in, the same posture as the email lookup beside it. It reads only `id` and the owner fields, and only the owner id leaves the resolver. An `owner_of:` audience on an object whose sharing model is `private` now resolves its owner; before, it resolved to nobody. + - **service-messaging, the rest of the fan-out and the outboxes.** The `role:` and `team:` membership reads, the email and SMS recipient reads, the notification template read, the dedup lookup in `emit()`, and both outboxes' enqueue, ack and list. + - **service-storage.** `StorageMetadataStore.createFile` and `createSession` insert under the opt-in. The organization still reaches the driver beside it, so the stored organization is unchanged, and the file's `owner_id` is still the uploading user. + - **service-settings.** The `sys_secret` store the plugin builds (insert, get, update), and the read that verifies a rotation before the old secret is reaped. A store `update` now writes the `ciphertext` it is given; without a context the engine's read-only strip dropped it. No caller in this repository uses `update`. + - **metadata-protocol.** `SysMetadataRepository.getByHash`, `list`, `history` and the history replay of `watch()`. + - None of the gates the middleware runs before its hand-off applies to these calls. ⛔ No new export on any package entry, and no new elevation API. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-messaging/package.json b/packages/services/service-messaging/package.json index 78297685516..7a79d82426a 100644 --- a/packages/services/service-messaging/package.json +++ b/packages/services/service-messaging/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-messaging", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Messaging Service for ObjectStack — outbound notification dispatch (ADR-0012). Ships the MessagingChannel registry, emit() fan-out, and the always-on inbox channel; other channels (email/webhook/push/IM) plug in.", "type": "module", diff --git a/packages/services/service-package/CHANGELOG.md b/packages/services/service-package/CHANGELOG.md index ad56f341a12..d3266d94432 100644 --- a/packages/services/service-package/CHANGELOG.md +++ b/packages/services/service-package/CHANGELOG.md @@ -1,5 +1,31 @@ # @objectstack/service-package +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-core@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-package/package.json b/packages/services/service-package/package.json index 55a21150c43..77a28852ad3 100644 --- a/packages/services/service-package/package.json +++ b/packages/services/service-package/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-package", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Package management service for ObjectStack — publish, install, and manage packages", "type": "module", diff --git a/packages/services/service-queue/CHANGELOG.md b/packages/services/service-queue/CHANGELOG.md index b7ade7b2233..429663ee502 100644 --- a/packages/services/service-queue/CHANGELOG.md +++ b/packages/services/service-queue/CHANGELOG.md @@ -1,5 +1,34 @@ # @objectstack/service-queue +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-queue/package.json b/packages/services/service-queue/package.json index ba925bba3ed..a34e68398d8 100644 --- a/packages/services/service-queue/package.json +++ b/packages/services/service-queue/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-queue", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Queue Service for ObjectStack — implements IQueueService with in-memory and durable DB-backed (sys_job_queue) adapters", "type": "module", diff --git a/packages/services/service-realtime/CHANGELOG.md b/packages/services/service-realtime/CHANGELOG.md index 3c2d77eeeb5..2f4e662f211 100644 --- a/packages/services/service-realtime/CHANGELOG.md +++ b/packages/services/service-realtime/CHANGELOG.md @@ -1,5 +1,58 @@ # @objectstack/service-realtime +## 17.8.0 + +### Minor Changes + +- 1920cf3: feat(platform-objects,service-automation,service-realtime)!: seven deployment-level platform tables lose their injected organization column, and reading them needs `manage_platform_settings` (ADR-0131 D7) + + Clause-②: no (narrowing) + + + + **BREAKING**, shipped as `minor` under the repo's launch-window convention for breaking changes (Changesets pre mode is not on yet). + + `sys_job`, `sys_job_run`, `sys_job_queue`, `sys_flow_dispatch`, `sys_migration`, `sys_migration_journal` and `sys_presence` hold deployment-level state. No writer attributes a row of any of them to an organization: every write is a system-context write whose row names none, and nothing writes `sys_presence` through ObjectQL at all. So the injected `organization_id` column only ever held NULL. ADR-0131 D7 takes it off: each object now declares `systemFields: { tenant: false }`. + + With no column there is no tenant wall, so these tables are governed by object permission. Each also declares `requiredPermissions: ['manage_platform_settings']`. Without that gate, a walled deployment's `organization_admin`, whose grant carries the superuser bits on every object, would read every other organization's job errors, queued payloads, dispatch keys and migration traces. + + **What moves for consumers.** + + - **The column.** `organization_id` is no longer a field of these seven objects. A filter, list-view column, report grouping, formula or seed key naming it on one of them is now an unknown field. Delete the reference: no organization owns a row of these tables. + - **Who reads, on a walled posture** (`group` or `isolated`). Before: the wall compared the NULL column to the caller's organization, so every reader got zero rows, platform administrators included (unless the deployment declared the table platform-global, which stood the wall down). Now: a principal holding `manage_platform_settings` (platform administrators hold it) lists every row; anyone else is refused `403 PERMISSION_DENIED`. + - **Who reads, on the `single` posture.** Before: any principal with a read grant on the object read every row, an organization administrator included. Now: only a principal holding `manage_platform_settings` reads; an organization administrator who is not a platform administrator is refused `403 PERMISSION_DENIED`. Grant the capability to an operator who needs these tables. + + **Unchanged.** Every platform writer and reader of these tables uses a system context, which no capability gate applies to, so job scheduling, the queue, flow dispatch, migration flags and the migration journal behave as before. The physical unique indexes are unchanged: none of these objects declares an organization-scoped one. + + **Existing databases.** Schema sync only adds, so the physical `organization_id` column stays on each existing table (with its index, where the deployment indexed it), and the boot drift report names it orphaned. By the writer census it holds only NULL, so dropping it loses nothing: `os migrate apply --allow-destructive` drops it, the remedy the drift report names. + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-realtime/package.json b/packages/services/service-realtime/package.json index 1fcbeee339d..fbd86e4a41d 100644 --- a/packages/services/service-realtime/package.json +++ b/packages/services/service-realtime/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-realtime", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Realtime Service for ObjectStack — implements IRealtimeService with WebSocket and in-memory pub/sub", "type": "module", diff --git a/packages/services/service-settings/CHANGELOG.md b/packages/services/service-settings/CHANGELOG.md index 41a4657bfdd..386cd4ee190 100644 --- a/packages/services/service-settings/CHANGELOG.md +++ b/packages/services/service-settings/CHANGELOG.md @@ -1,5 +1,47 @@ # @objectstack/service-settings +## 17.8.0 + +### Patch Changes + +- b88c356: The remaining platform producers in these four packages now pass the explicit system opt-in (`{ isSystem: true }`) on their data-engine calls. Until now they reached the engine with no principal and no opt-in, and the security middleware let that through only because of its principal-less hand-off. + + Clause-②: no + + - **service-messaging, the inbox read state.** `listInbox` (and its unread total), the receipt read behind it, and mark-read / mark-all-read take the opt-in inside the service. Their scope is unchanged: every read of a user's rows is keyed on the user id the door derived from the session, the receipt a mark-read inserts is stamped with it, and the receipt it updates is one a user-keyed read returned. + - **service-messaging, `owner_of:` audiences.** The record read takes the opt-in, the same posture as the email lookup beside it. It reads only `id` and the owner fields, and only the owner id leaves the resolver. An `owner_of:` audience on an object whose sharing model is `private` now resolves its owner; before, it resolved to nobody. + - **service-messaging, the rest of the fan-out and the outboxes.** The `role:` and `team:` membership reads, the email and SMS recipient reads, the notification template read, the dedup lookup in `emit()`, and both outboxes' enqueue, ack and list. + - **service-storage.** `StorageMetadataStore.createFile` and `createSession` insert under the opt-in. The organization still reaches the driver beside it, so the stored organization is unchanged, and the file's `owner_id` is still the uploading user. + - **service-settings.** The `sys_secret` store the plugin builds (insert, get, update), and the read that verifies a rotation before the old secret is reaped. A store `update` now writes the `ciphertext` it is given; without a context the engine's read-only strip dropped it. No caller in this repository uses `update`. + - **metadata-protocol.** `SysMetadataRepository.getByHash`, `list`, `history` and the history replay of `watch()`. + - None of the gates the middleware runs before its hand-off applies to these calls. ⛔ No new export on any package entry, and no new elevation API. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/services/service-settings/package.json b/packages/services/service-settings/package.json index daf7abcc816..fe1083ca143 100644 --- a/packages/services/service-settings/package.json +++ b/packages/services/service-settings/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-settings", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Settings service for ObjectStack — manifest registry + K/V resolver (OS_* env > Tenant > User > Default) + REST routes. See ADR-0007.", "type": "module", diff --git a/packages/services/service-sms/CHANGELOG.md b/packages/services/service-sms/CHANGELOG.md index ecd29b40f57..404844a659c 100644 --- a/packages/services/service-sms/CHANGELOG.md +++ b/packages/services/service-sms/CHANGELOG.md @@ -1,5 +1,33 @@ # @objectstack/service-sms +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [aa71c4d] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [4935c66] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/plugin-auth@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/services/service-sms/package.json b/packages/services/service-sms/package.json index 76c360fd2d4..02d8852e4d7 100644 --- a/packages/services/service-sms/package.json +++ b/packages/services/service-sms/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-sms", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "SMS service for ObjectStack — ISmsService + transport-pluggable outbound delivery (Aliyun / Twilio / log).", "main": "dist/index.js", diff --git a/packages/services/service-storage/CHANGELOG.md b/packages/services/service-storage/CHANGELOG.md index a1fa78acdfd..553617e68d3 100644 --- a/packages/services/service-storage/CHANGELOG.md +++ b/packages/services/service-storage/CHANGELOG.md @@ -1,5 +1,56 @@ # @objectstack/service-storage +## 17.8.0 + +### Patch Changes + +- a7a48b7: The storage store's by-id methods and the HTTP outbox's `redeliver` now pass the explicit system opt-in (`{ isSystem: true }`) on their data-engine calls. Until now they reached the engine with no principal and no opt-in, and the security middleware let that through only because of its principal-less hand-off. + + Clause-②: no + + - **service-storage.** `StorageMetadataStore.getFile`, `updateFile`, `deleteFile`, `getSession`, `updateSession` and `deleteSession` take the opt-in inside the store. Access stays by id, and the reads stay unscoped by organization, as before. On update and delete the acting organization still reaches the driver beside the opt-in, so a row stamped for another organization is still out of reach of these doors. The doors keep the authorization they already ran. + - **service-storage, the update payload.** `updateFile` and `updateSession` now send the caller's patch alone, where they used to send the whole row read back merged with it. The engine's read-only strip, which does not run for a system write, used to take `organization_id` and the four audit columns out of that row; now the store never sends them. The stored row is the same as before, and a column another writer changed between the read and the write is no longer reverted by it. + - **service-messaging.** `SqlHttpOutbox.redeliver` takes the opt-in on both of its reads and on its reset write. The caller's `tenantId` stays on every call as the driver-level scope, so a delivery in another organization is still not found. The reset write states `bypassTenantAudit: false`, so a redelivery from a caller with no organization is still reported by the driver's tenant audit. + - None of the gates the security middleware runs before its hand-off applies to these calls. ⛔ No new export on either package entry, and no new elevation API. +- b88c356: The remaining platform producers in these four packages now pass the explicit system opt-in (`{ isSystem: true }`) on their data-engine calls. Until now they reached the engine with no principal and no opt-in, and the security middleware let that through only because of its principal-less hand-off. + + Clause-②: no + + - **service-messaging, the inbox read state.** `listInbox` (and its unread total), the receipt read behind it, and mark-read / mark-all-read take the opt-in inside the service. Their scope is unchanged: every read of a user's rows is keyed on the user id the door derived from the session, the receipt a mark-read inserts is stamped with it, and the receipt it updates is one a user-keyed read returned. + - **service-messaging, `owner_of:` audiences.** The record read takes the opt-in, the same posture as the email lookup beside it. It reads only `id` and the owner fields, and only the owner id leaves the resolver. An `owner_of:` audience on an object whose sharing model is `private` now resolves its owner; before, it resolved to nobody. + - **service-messaging, the rest of the fan-out and the outboxes.** The `role:` and `team:` membership reads, the email and SMS recipient reads, the notification template read, the dedup lookup in `emit()`, and both outboxes' enqueue, ack and list. + - **service-storage.** `StorageMetadataStore.createFile` and `createSession` insert under the opt-in. The organization still reaches the driver beside it, so the stored organization is unchanged, and the file's `owner_id` is still the uploading user. + - **service-settings.** The `sys_secret` store the plugin builds (insert, get, update), and the read that verifies a rotation before the old secret is reaped. A store `update` now writes the `ciphertext` it is given; without a context the engine's read-only strip dropped it. No caller in this repository uses `update`. + - **metadata-protocol.** `SysMetadataRepository.getByHash`, `list`, `history` and the history replay of `watch()`. + - None of the gates the middleware runs before its hand-off applies to these calls. ⛔ No new export on any package entry, and no new elevation API. +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/observability@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/services/service-storage/package.json b/packages/services/service-storage/package.json index 66dfc997f46..d0d626519c9 100644 --- a/packages/services/service-storage/package.json +++ b/packages/services/service-storage/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/service-storage", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Storage Service for ObjectStack — implements IStorageService with local filesystem and S3 adapter skeleton", "type": "module", diff --git a/packages/spec/CHANGELOG.md b/packages/spec/CHANGELOG.md index ef61443c743..62f6fb616b5 100644 --- a/packages/spec/CHANGELOG.md +++ b/packages/spec/CHANGELOG.md @@ -1,5 +1,297 @@ # @objectstack/spec +## 17.8.0 + +### Minor Changes + +- 1920cf3: feat(platform-objects,service-automation,service-realtime)!: seven deployment-level platform tables lose their injected organization column, and reading them needs `manage_platform_settings` (ADR-0131 D7) + + Clause-②: no (narrowing) + + + + **BREAKING**, shipped as `minor` under the repo's launch-window convention for breaking changes (Changesets pre mode is not on yet). + + `sys_job`, `sys_job_run`, `sys_job_queue`, `sys_flow_dispatch`, `sys_migration`, `sys_migration_journal` and `sys_presence` hold deployment-level state. No writer attributes a row of any of them to an organization: every write is a system-context write whose row names none, and nothing writes `sys_presence` through ObjectQL at all. So the injected `organization_id` column only ever held NULL. ADR-0131 D7 takes it off: each object now declares `systemFields: { tenant: false }`. + + With no column there is no tenant wall, so these tables are governed by object permission. Each also declares `requiredPermissions: ['manage_platform_settings']`. Without that gate, a walled deployment's `organization_admin`, whose grant carries the superuser bits on every object, would read every other organization's job errors, queued payloads, dispatch keys and migration traces. + + **What moves for consumers.** + + - **The column.** `organization_id` is no longer a field of these seven objects. A filter, list-view column, report grouping, formula or seed key naming it on one of them is now an unknown field. Delete the reference: no organization owns a row of these tables. + - **Who reads, on a walled posture** (`group` or `isolated`). Before: the wall compared the NULL column to the caller's organization, so every reader got zero rows, platform administrators included (unless the deployment declared the table platform-global, which stood the wall down). Now: a principal holding `manage_platform_settings` (platform administrators hold it) lists every row; anyone else is refused `403 PERMISSION_DENIED`. + - **Who reads, on the `single` posture.** Before: any principal with a read grant on the object read every row, an organization administrator included. Now: only a principal holding `manage_platform_settings` reads; an organization administrator who is not a platform administrator is refused `403 PERMISSION_DENIED`. Grant the capability to an operator who needs these tables. + + **Unchanged.** Every platform writer and reader of these tables uses a system context, which no capability gate applies to, so job scheduling, the queue, flow dispatch, migration flags and the migration journal behave as before. The physical unique indexes are unchanged: none of these objects declares an organization-scoped one. + + **Existing databases.** Schema sync only adds, so the physical `organization_id` column stays on each existing table (with its index, where the deployment indexed it), and the boot drift report names it orphaned. By the writer census it holds only NULL, so dropping it loses nothing: `os migrate apply --allow-destructive` drops it, the remedy the drift report names. +- ac9f8bd: A builtin flow node's `config` value that its executor contract refuses is refused at parse, with a location, in the contract's own words: `create_record` `outputVariable: 42`, a screen field `min: '1'`, a `get_record` `limit: '10'` and the like no longer pass the build doors and then fail every run. + + Clause-②: yes (narrowing) + + + + **BREAKING**: an accept-set narrowing on a published authoring surface, shipped as `minor` under the launch-window convention for accept-set narrowings. + + **Why.** Every builtin executor parses its node's `config` against the contract `getBuiltinNodeConfigContracts()` names before it acts, and refuses the node on any finding. The build doors judged only the keys that contract requires, left out, so a present value it refuses passed `FlowSchema.parse`, `objectstack validate` and `objectstack compile` (compile copied it into `dist/objectstack.json`), registered, and failed every run that reached the node: `create_record 'mk': config does not satisfy the create_record contract — config.outputVariable: Invalid input: expected string, received number`. + + **What is refused.** A node of any builtin type (`get_record`, `create_record`, `update_record`, `delete_record`, `notify`, `http`, `screen`, `script`, `subflow`, `map`, `loop`, `parallel`, `try_catch`), at any depth, whose present config value its executor contract refuses — a wrong type, a value outside the declared set or range, an empty `function` / `flowName`, or a rule finding on present keys (a `notify` `template` beside an inline `title`). The refusal is the existing closed-set code `node-config-refused-by-contract`, `params: { nodeType, key }`, anchored at the key (`nodes.N.config.outputVariable`, `nodes.N.config.fields.0.min`), from the one judge `flowNodeConfigRefusals` that `FlowSchema.parse`, `AutomationEngine.registerFlow` (which parses first) and `objectstack validate` share. The issue's `code` is `custom`. That covers `FlowSchema`, `defineFlow()`, `defineStack` (`STACK_SCHEMA_INVALID`, 422, at `flows.N.nodes.M.config.`), `os validate`, `os compile`, an artifact's parse, `registerFlow` and the metadata save door (`422 INVALID_METADATA`). + + **What the build doors still accept, byte for byte.** Every value its contract accepts, and the values this arm holds back: + + - a value carrying a `{token}` (also spelled with double braces or a leading `$`) — never refused at the build doors for its pre-interpolation type. That is not a promise it runs: only `http` interpolates its config before it parses, so only an `http` slot sees the token's resolved value. Every other builtin parses its config as authored, so a token in one of its number or boolean slots (`limit: '{n}'`, `maxIterations: '{cap}'`, a screen field `min: '{m}'`, `multi: '{bulk}'`) still fails at its first run, exactly as before — write a literal there; + - on `http`, any value with a token inside it, and `signingSecret` (the credential channel may supply it); + - a `loop` with no `body` (its executor does not parse it), and the region slots of `loop`, `parallel` and `try_catch`; + - an undeclared or retired key, a screen field's `visibleWhen` and a CRUD `fields` value — each keeps the judge it had. + + ## FROM → TO + + | you wrote | write instead | + |:--|:--| + | `outputVariable: 42` | `outputVariable: 'taskId'` — the variable's name | + | a screen field `min: '1'`, `max: '10'` | `min: 1`, `max: 10` | + | `limit: '10'`, `maxIterations: '5'` (any number slot outside `http`) | `limit: 10`, `maxIterations: 5` — a literal number only: these executors parse the config as authored, so a `{token}` here passes the build and fails every run | + | `multi: 'true'`, a screen field `required: 'yes'` (any boolean slot outside `http`) | `multi: true`, `required: true` — a literal boolean only, for the same reason | + | `http` `timeoutMs: '5000'`, `durable: 'yes'` | `timeoutMs: 5000`, `durable: true` — or, on `http` alone, a sole-token template such as `timeoutMs: '{timeout}'`: `http` interpolates before it parses, so the token resolves to its value's type first | + | `severity: 'loud'`, `mode: 'view'` | one of the declared values (`'info'` / `'warning'` / `'critical'`; `'create'` / `'edit'`) | + | a `notify` with both `template` and `title` | one content path, as the refusal's sentence says | + + **The one-line fix: write the value the contract declares at the key the refusal names.** The runtime never ran such a node, so the fix changes nothing a working flow does. + + **Who is affected, measured.** At `833d57c9cf`, every builtin node `config` authored in this repository's examples, docs, skills and `packages/qa` fixtures (96 nodes), and every one in hotcrm at `4054ec2680` (138 nodes), parses under this arm. A second census at `d1c7d8d392` that also reads helper calls, same-file constants and assignments into a node config (1065 configs in this repository, 138 in hotcrm) found no other real writer; 64 configs here take a value from an import, a call or a spread that no static reading evaluates, and are not counted either way. The one real writer found to store a refused value is the Studio flow designer, which saved a screen field's Min / Max as strings until objectui `5ba255538a`. Deployed metadata, and other repositories, were not measured. Where such a node already sits in a stored flow, the whole flow is refused at registration: at boot it is skipped with a warn naming it, its trigger not armed, while the flows beside it register. + + ### The kit + + - **The refusal.** The value half of the executor-contract arm of `flowNodeConfigRefusals` in `automation/flow-node-config-refusals.ts`; no new code joins `FLOW_SLOT_REFUSAL_CODES`, and `getBuiltinNodeConfigContracts()` keeps its 13 entries. + - **The ledger.** The D3 semantic entry `flow-builtin-node-config-values-refused` (protocol 18). No key is removed, so there is no tombstone, and there is no D2 conversion: the platform cannot know the value the author meant. +- c565813: feat(spec,analytics): a dataset answer's measure column states its aggregate, labelled or not (`fields[].aggregate`) + + Clause-②: yes (widening) + + - **What a renderer can now read.** Each measure column of a dataset answer (`POST /analytics/dataset/query`) carries `fields[].aggregate`: the aggregate its dataset measure declares, in the closed `AggregationFunction` vocabulary (`count`, `sum`, `avg`, `min`, `max`, `count_distinct`). It is there whether or not the author gave the measure a `label`. So a chart can tell a count from a sum, for example to draw whole-number axis ticks for a count instead of 0.75 / 1.5 / 2.25. + - **What was missing.** The only aggregate on the wire was `builtinAggregate`, and it is present only when the measure has no `label`. A labelled measure, such as a `count` named "Tasks", reached the wire as `{ name, type: 'number', label }`, with nothing to say what kind of number it was. + - **Where it is set.** `AnalyticsService` writes it in the one step that describes a dataset answer's columns from the dataset's own measures. That step runs for both the live query and the draft-data preview, so the two answers agree. A measure's `__compare` column carries the same aggregate. + - **Where it is absent.** Dimension columns. Derived measures, which combine other measures and have no single aggregate (a stray `aggregate` written beside `derived` is ignored when the dataset compiles, so it is not stated here either). And a cube query answer (`POST /analytics/query`), which does not run through the dataset column step. + - **Unchanged.** `builtinAggregate` keeps its meaning: present only on a label-less measure column, to mark a header that is the server's default. No authoring key is added; `aggregate` is a response member only. `AnalyticsResultResponseSchema` and the `AnalyticsResult` contract declare the member, and the REST route relays it as it does every other column key. +- 56c8844: `@objectstack/spec/ui` now exports the rule that decides which forms a `view` body opens to anonymous intake, so a console reads "published" from the same rule the server's anonymous form doors serve + + Clause-②: yes (widening) + + - **New on `@objectstack/spec/ui`:** `publicFormSlug`, `anonymousFormIntakeSlug`, `anonymousFormIntakeCandidates`, `anonymousFormIntakeSlugs` and the `AnonymousFormIntakeCandidate` type. They lived only in `@objectstack/metadata-core`, which a browser console should not depend on. They are pure functions with no imports, beside the `SharingConfigSchema` they read. + - **What they decide is unchanged.** A form is open when its `sharing` has `enabled === true`, `allowAnonymous === true` and a non-empty `publicLink`. The scan covers the same three shapes in the same order: the nested `form`, every `formViews` entry, then the `config` of a `viewKind: 'form'` item. + - **`@objectstack/metadata-core` re-exports the same functions** from `@objectstack/spec/ui`. They are the spec's own bindings, not wrappers or copies, so there is still one copy of the rule. Its exports, names and types are unchanged, and `@objectstack/rest` and `@objectstack/metadata-protocol` keep importing from it. Its built output now loads `@objectstack/spec/ui` to get them. + - **Not covered by the new export:** whether another metadata layer withdraws a form (`anonymousFormIntakeWithdrawnIn`), and whether the deployment's tenancy posture lets the form take an anonymous submission (`anonymousFormIntakeUnavailability`). These two read server state and stay in `@objectstack/metadata-core`. `anonymousFormObjectName`, which names the object a form submits into, stays there beside them; it is a pure read of the form and the view, not of server state. A form the new functions call open can still be withheld by a withdrawal in another layer or by the posture. +- d4680d2: feat(spec)!: `NotifyConfigSchema.title` and `NotifyConfigSchema.message` are template slots: each takes a bare string or a `{ dialect: 'template', source }` envelope (the `tmpl` helper), as the expression dialect table already listed notification subjects and bodies among the `template` slots, and a blank bare string is now refused there + + Clause-②: yes (narrowing) + + + + **BREAKING** accept-set narrowing at two authorable keys (`automation/NotifyConfig:title`, `automation/NotifyConfig:message`), shipped as `minor` under the repo's launch-window convention for breaking changes. It is the grade `TemplateExpressionInputSchema`'s blank-string rule shipped with when it reached the first twelve typed keys. + + - **What widens.** Both keys are typed with `TemplateExpressionInputSchema`, the input every other `template` slot uses. Before, both were `z.string()`, so a notify node written with `` tmpl`…` `` passed `defineFlow` and registration and then failed every run at the execute-time contract parse (`expected string, received object`). It now parses and runs. + - **What narrows.** A blank bare string (`''` or whitespace-only) at either key is newly refused, by the shared template input's non-blank rule (`invalid_union`, with the `TYPED_EXPRESSION_SOURCE_REQUIRED.template` sentence). Before, every blank value parsed: + - `title: ''` then failed every run at the executor's guard ("notify: title is required"), so it fails either way, now earlier; + - a whitespace-only `title` passed that guard and was delivered as the notification title, and it is now refused; + - `message: ''` or a whitespace-only `message` was delivered as an empty or blank body, and it is now refused. + + The fix is to write the text, or to delete the key (`message` is optional). + - **Parse output.** `NotifyConfigSchema.parse(...).title` and `.message` go from `string` to `{ dialect: 'template', source }`, for both spellings, because the parse normalizes a bare string to that envelope. The exported `NotifyConfigParsed` type changes with them. Code that reads parse output reads `.source`. The `notify` executor, the one reader in this repo, now does, so both spellings of one text deliver the same `payload.title` and `payload.body`, and a bare string renders exactly what it rendered before. + - **Still refused, with a new sentence.** A value that is neither a string nor a template envelope (a number, an array, a `cel` envelope) was refused before (`invalid_type`). It is refused now as `invalid_union`, with the `TYPED_EXPRESSION_DIALECT_ONLY.template` sentence. + - **New, notify-only.** A template envelope on either key must carry a non-blank `source`. The executor renders `source` and has nothing to render from `ast` alone, so such an envelope is refused at the key instead of failing every run (`title`) or sending an empty body (`message`). An envelope never parsed at these keys before, so this refuses nothing that used to parse. + - **Placeholder spelling.** These two slots read the flow's single-brace `{token}` (`{record.name}`). A `{{var}}` is not a placeholder here: the inner `{var}` resolves and the outer braces stay in the text, for a bare string and an envelope alike. The `.describe()` on both keys now says so, and no longer says the text is "sent verbatim". + - `@objectstack/service-automation`: the `notify` executor reads `source` from the two template slots, and the descriptor's `title` / `message` descriptions state the `{token}` interpolation in place of "sent verbatim". +- cdeabec: `os migrate meta` stops listing semantic notices whose surface the stack provably does not declare. It counts them instead, and `--all` lists them in full. + + Clause-②: yes + + - **`SemanticMigration.relevantWhen`** (`@objectstack/spec/migrations`) is a new optional field. It holds a structured question over the loaded stack, `{ kind: 'stack-declares', keys: [...] }`: does the stack declare anything under one of these top-level keys? A key's value in a `packages[].manifest` body counts the same as a top-level value. The question is closed and named. It is never free text, and it never matches against the prose of `surface`. The new types `SemanticRelevance`, `StackDeclaresRelevance` and `SemanticRelevanceKey` are exported beside `SemanticMigration`. + - **`applyMetaMigrations`** asks each entry's question of the stack it is given and of every hop checkpoint. + - **`todos` is unchanged.** `MigrationChainResult.todos` and `MigrationHopResult.todos` still hold every semantic entry of every hop crossed, whatever the stack holds, as before. + - **`absentTodos` is a new required member** of `MigrationChainResult` and `MigrationHopResult`. It names the subset of `todos` whose question answered `absent` in all of them: the same objects, in chain order. Code that only reads a chain result needs no change. Code that builds one of these two interfaces itself must now supply `absentTodos` (an empty array when nothing is proven absent). + - **Only a positive proof names an entry.** These cases answer `unknown` and leave it off `absentTodos`: + - a value the question cannot read (a function, a promise, a scalar, a getter that throws); + - a stack that is not a plain object; + - any `plugins`, `devPlugins` or `tiers` entry, since a plugin, or the platform plugins a tier preset loads, can register metadata the stack does not show. + + An entry that judges a conversion which applied an edit in the same run is never named either. + - **The first batch is 25 entries** (5 from protocol 17, 20 from protocol 18). Each one's surface lives only under named top-level stack keys: `analyticsCubes`, `apis`, `jobs`, `mappings`, `hooks`, `agents`, `tools`, `dashboards` (with `reports` and `pages` for the chart-config entry), `datasets`, `permissions` and `sharingRules`. None of them names a code door. Each entry was also checked to confirm that its acceptance criteria send the author to no stored row and no runtime door. Every other entry is never named absent, so it is listed exactly as before. + - **`os migrate meta`** lists `todos` minus `absentTodos`. After the listed notices it prints one line that counts the entries proven absent and names `--all`. A second line says that the proof covers the stack this run loaded, and not metadata a deployment stores. + - `--all` prints each of those entries in full, with the keys it was proven absent under. + - `--json` keeps `todos` whole and adds `absentTodos`, plus `hops[].absentTodos` with `--step`. + - `--step` reports each hop's listed count, and adds a `not listed` count to the hop line when that count is not zero. + - A run whose only notices are proven absent still writes `--out`. +- db4c45b: feat(spec)!: `FlowSchema` refuses an edge whose `source` or `target` names no node of its graph, and an edge that repeats an earlier one + + Clause-②: no (narrowing) + + + + **BREAKING**: an accept-set narrowing on a published authoring surface, shipped as `minor` under the launch-window convention for accept-set narrowings. + + **Why.** `FlowSchema` held node ids and edge ids unique, and checked nothing else about an edge. A flow whose edge named a node it no longer held (`start → node_1` over nodes `[start, end]`), or that held `start → node_1` three times, passed `FlowSchema.parse`, `objectstack validate` and the metadata save door, and publish answered 200 with `_diagnostics.valid: true`. Then it ran. The dangling edge carried the run nowhere, silently, and the repeated edge ran its target once per copy: one record update created three identical records. The Studio flow designer produced both shapes after a node was removed. + + **What is refused.** Both rules run in the region walk the node-id rule already uses, at every depth it reaches. The issue's `code` is `custom`, and each issue is anchored on the edge to fix: + + - **An endpoint that names no node of the edge's own graph**, at `edges.N.source` / `edges.N.target`. For an edge inside a `loop` / `parallel` / `try_catch` region the path is the region path, such as `nodes.N.config.body.edges.M.target`. The graph is the flow's own `nodes` for a top-level edge, and the region body's `nodes` for a region edge, because the engine resolves an endpoint there alone. So a top-level edge into a region node is refused too, and the message names the graph the node does live in. The node-id space is still one across the flow for uniqueness. + - **A repeated edge**, at `edges.N`, naming the earlier copy. Repeated means the key the engine selects on: the same `source`, `target`, `type`, `condition` (its dialect and source, so a bare CEL string and its envelope are one condition) and branch `label`. An `isDefault` copy of an unconditional edge is a repeat. A repeat is judged only between edges whose endpoints both resolve. + + That covers `FlowSchema`, `defineFlow`, `defineStack` (`STACK_SCHEMA_INVALID`, 422), `os validate`, `os compile`, an artifact's parse, `AutomationEngine.registerFlow` (which parses first) and the metadata save door (`422 INVALID_METADATA`, in draft and in publish mode). + + **What is still accepted, byte for byte.** Two nodes joined by edges the engine tells apart: different conditions, a `fault` edge beside a default one, or `approve` and `reject` branch labels into one node. Every flow whose edges all resolve in their own graph and repeat nothing. + + ## FROM → TO + + | you wrote | write instead | + |:--|:--| + | an edge into a node that is not in the same `nodes` list (`target: 'node_1'`, no `node_1`) | point it at the node it was meant to reach, or delete the edge | + | a top-level edge into a node inside a region body | an edge into the region's container node; the region's own edges reach the nodes inside it | + | the same `source` → `target` edge twice, with the same `type`, `condition` and `label` | one edge. Delete the later copy; an edge meant to take its own route needs its own `condition` or branch `label` | + + **The one-line fix: delete the edge the refusal names, or re-point its endpoint.** Deleting a dangling edge changes nothing a run did, with one exception. A conditioned edge into a missing node still counted as the branch taken when its condition held, so where a flow relied on that, point the edge at a node that ends the branch. Deleting a repeated copy runs its target once per traversal instead of once per copy, which is the defect being removed. + + **Who is affected, measured.** At `aa71c4d9d`, every flow the examples ship (`app-showcase`, `app-crm`, `app-todo`: 35 flows, 55 graphs counting region bodies, 131 edges) has no dangling endpoint and no edge pair sharing a `source` and `target` at all. The flows the packages ship (the `os generate` and `os explain` templates, the new-flow seed, the `@objectstack/verify` fixture) and the platform test checklist's flow bodies are clean by reading. The CLI's golden eval corpus carries no flow. Deployed metadata and other repositories were not measured. Where such an edge already sits in a stored flow, the whole flow is refused at registration: at boot it is skipped with a warn naming it, its trigger not armed, while the flows beside it register. + + ### The kit + + - **The refusal.** Two blocks in `FlowSchema`'s `superRefine`, after the edge-id rule, over `collectFlowGraphs`. No new error code: the issue is the same `custom` issue the id rules raise. + - **The ledger.** The D3 semantic entry `flow-edge-unresolved-or-repeated-refused` (protocol 18) and its step-18 rationale fragment. No key is removed, so there is no tombstone, and there is no D2 conversion: a dangling endpoint carries no intent a rewrite could recover, and dropping a copy changes how often its target runs. +- ace0a53: feat(lint)!: `relationship/master-detail-required` refuses the three unsafe master-reference shapes at `error` on a `controlled_by_parent` object (#9139) + + Clause-②: no (narrowing) + + A `controlled_by_parent` detail derives all of its record access from the master its `master_detail` reference names (ADR-0055). Three declarable shapes of that reference leave the security gate as the only thing refusing a detail record saved without its master, because record validation never checks a field that is not `required` and skips `readonly` and `system` fields before its required check: + + 1. `required` absent, or `required: false`; + 2. `required: true` with `readonly: true`; + 3. `required: true` with `system: true`. + + A record that lands without its master anyway is readable by nobody, and every later write to it by id is refused. Until now `relationship/master-detail-required` was a `warning` with the predicate "`required` is not `true`", on every object, so shapes 2 and 3 drew no finding at any severity. The maintainer ruling of 2026-08-16 (Direction 1) scheduled the promotion for the v18 boundary, scoped to `controlled_by_parent`. + + **BREAKING — what moves for consumers.** + + - `os lint` reports each of the three shapes at `error` when the object declares `sharingModel: 'controlled_by_parent'`, located at the defect (`…fields.FIELD.required`, `.readonly` or `.system`). It covers every `master_detail` field of such an object, the same scope the builder's `required: true` force already applies. `os lint` therefore exits non-zero on such a stack, and the metadata-generation rubric (`scoreMetadata`) weighs the finding as an error and marks the stack `valid: false`. + - `@objectstack/spec` gains the step-18 semantic migration entry `cbp-master-detail-required-lint-error`, so `os migrate meta` across protocol 18 prints the prescription below. + + **Remedy — the v18 upgrade-checklist line.** On every object with `sharingModel: 'controlled_by_parent'`, give each `master_detail` reference `required: true` and remove any `readonly: true` or `system: true` from it. `os lint` now refuses the missing-`required`, `required` + `readonly` and `required` + `system` shapes there at `error` (`relationship/master-detail-required`). An object authored through `ObjectSchema.create` already gets `required: true` when the key is omitted, so the edit there is dropping the flag. + + **Unchanged.** + + - On every object that is not `controlled_by_parent` the rule is exactly as before: a `warning` for a `master_detail` without `required: true`, the same message and fix, and no finding for the two flagged shapes. + - The rule is not in the authoring-rule registry. `os build`, `os validate` and the metadata save door do not run it, so a stack carrying one of the shapes still builds and publishes. Only `os lint`'s exit code and the generation rubric move. + - Runtime is untouched. The security gate keeps refusing an insert that omits the master FK on these shapes and keeps resolving the master for metadata already at rest, and stored metadata is neither rewritten nor refused on load. + - No export or signature moves in either package. + - Measured before crossing, at `b04a5295f`: 129 authored objects across the example apps, the platform, plugin and service objects and the CLI's golden eval corpus. 7 of them are `controlled_by_parent`, and 0 draw the new `error`. + + + +### Patch Changes + +- 0af4f66: Three `ComponentPropsMap` read-point records now quote the objectui line they cite + + Clause-②: no + + The docblocks of `action:button`, `action:icon` and `element:definition-list` in + `src/ui/component.zod.ts` each quote the first line of the row's props-read site, re-read + against objectui at the pin this package builds against (`a58626c88`): the runner-forward + literal of the two action blocks, and the `readProps` call of the definition list. A pin bump + that moves one of those lines, or changes it, now fails `check:objectui-pin-citations` and + names where the quoted line went, where before only the cited sha was checked. The other + three rows of that section (`action:group`, `action:menu`, `element:repeater`) carry no quote + yet. Comment text only: no schema, key, type or export changes. +- 9a0401f: The `action:group`, `action:menu` and `element:repeater` read-point records are re-measured at the objectui pin and quote the line they cite + + Clause-②: no + + The docblocks of `action:group`, `action:menu` and `element:repeater` in `src/ui/component.zod.ts` + cited objectui lines that had moved without any gate noticing: the two containers' anchors by 3 to 8 + lines since objectui#11638, every repeater anchor by 28 lines since objectui#11168 slice 2, and the + repeater's `data-objectstack` filter and sort anchors since earlier pins. Each is now re-pointed at + the pin this package builds against (`a58626c88`), as are `action:button`'s `static-params.ts` + citation and the `element:definition-list` registration notes, which said the registration + publishes the strings `'1'` / `'2'` and marks `items` required (it no longer does either). Each of + the three rows now quotes the first line of its props-read site (the member forward of the two + containers, the `readProps` call of the repeater), so a pin bump that moves or changes one of those + lines fails `check:objectui-pin-citations`. All six rows of that section now carry a quote. Comment + text only: no schema, key, type or export changes. +- 04e776b: `App.defaultAgent` docblock: the agent route is the one chat door, and the console is what reads the key. + + Clause-②: no + + - The docblock no longer says the assistant chat endpoint (`POST /api/v1/ai/assistant/chat`) resolves this agent from `context.appName`. That route, with `GET /api/v1/ai/assistant` and `GET /api/v1/ai/assistant/skills`, was retired in the cloud AI runtime (objectstack-ai/cloud#2621, objectstack-ai/cloud#2651), and no server route reads `defaultAgent`. + - It now says who does read it. The console's chat dock hands the active app's `defaultAgent` to its one surface-to-agent resolver, which honours only `ask` or `build` (legacy aliases included) and otherwise falls back to the surface default. The resolved agent is then called by name on `POST /api/v1/ai/agents/:agentName/chat`, where the path segment, not this key, selects the agent. + - The ADR-0063 surface-binding paragraph and the rule that only the two platform agents resolve are unchanged, as is the note that the bare `POST /api/v1/ai/chat` resolves no agent. + - The docs page `ai/actions-as-tools` lists the agent route as the only in-product chat route. + - ⛔ No schema, parse, `.describe()`, export, type or accept-set change. The docblock ships in the published package, in the `dist/ui` and `dist/browser` JavaScript bundles and in the shipped `src/ui/app.zod.ts`, which is why this is a patch. +- a7df552: `ObjectNavItemSchema.viewName`'s describe no longer says the default is "all". It now states what the console does when an object nav entry names no view: it opens the object's default list view, else its first declared list view. `all` is only the console's fallback tab, and it exists only for an object that declares no list view. + + Clause-②: no + + - The rule is read from objectui at the `.objectui-sha` pin. `ObjectView` opens `defaultViewId || views[0]`, where `defaultViewId` is the view `buildViewTabs` marks `isDefault` (the default `list`). `buildViewTabs` adds the `all` tab only when the object has no list view at all. + - An author who omitted `viewName` expecting all records got that default or first declared view instead. When the object declares more than one list view, name the one the entry should open in `viewName`. + - The generated app reference page follows (#21973). The lint header that quoted the old sentence follows too, as a comment only. + - ⛔ No schema, type, optionality, default, export or accept-set change. The console's behaviour does not change. +- d5a14dd: `EvalUserSchema.isPlatformAdmin` is no longer marked deprecated. Its describe and docblock now say what the key reports: the `PLATFORM_ADMIN` standing of ADR-0095 D3. + + Clause-②: no + + - The platform resolves that standing per request, from the deployment's declared administrator list (`OS_PLATFORM_OWNER_EMAIL`) under every tenancy posture, or from an unscoped `admin_full_access` grant under the `single` posture. + - It is the predicate platform-operator gates read: `current_user.isPlatformAdmin == true` (ADR-0068 D4). The session payload emits it from the posture rung, and the platform-admin route gate reads it. + - The resolver projects the `platform_admin` name into `positions` from the same grant, so the name and the key agree for every genuine administrator. Gate on the key, never on `'platform_admin' in current_user.positions`. + - The old text, "DERIVED alias of 'platform_admin' in positions. Deprecated.", described a reading ADR-0095 D3 superseded. ADR-0068 carries dated notes under D2 and D4 that say so. + - ⛔ Nothing you author changes. There is no schema, type, optionality, default, export or accept-set change, and `createEvalUser` computes exactly what it did. A predicate that already reads `current_user.isPlatformAdmin` keeps working and is the supported form. +- 93125ae: A browser bundle that imports from `@objectstack/spec/shared` or the package root no longer keeps the ADR-0087 conversion table unless it uses it + + Clause-②: no + + Each published entry is one flat file, so a consumer's bundler keeps every top-level call it cannot prove pure, together with everything that call references. Two such calls built the conversion table when the module loaded: the major-18 list's `inApplicationOrder(...)` and the flattening into `ALL_CONVERSIONS`. So every bundle of an entry that reaches the table kept all of it: every conversion, plus the view, field, page-component, dashboard, chart and report schemas the conversions read. `./shared` reaches the table only through `normalizeStackInput`, so a bundle that imported an expression schema from it carried the table too, and 17.7.0's new conversions made that copy larger. Both calls now carry a `@__PURE__` annotation, so a bundle keeps the table only when something it keeps reads it, for example `defineStack`, `normalizeStackInput` or `applyConversions`. + + Measured on objectui's console (objectui `c0862c1c`), against the same build with this package's previous source: the first screen's eager closure is 226,238 bytes gzip smaller, all of it in the `vendor-objectstack` chunk. Every entry's export list, every declaration and every runtime value is unchanged. Only the bytes a bundler keeps change. +- 15ec50e: A notify flow node's `title` / `message` refusal now prescribes `'{record.name}'`, the single-brace spelling the notify executor reads. It used to prescribe the shared template sentence's `'{{record.name}}'`, which the build's `flow-double-brace-interpolation` rule then flagged on the same node and the notify renderer sent inside a stray pair of braces. + + Clause-②: no + + - Both slots take the same input as before: a bare, non-blank string or a `{ dialect: 'template', source }` envelope. Every value that parsed still parses, every value that was refused is still refused, with the same `invalid_union` code at the same path. Only the sentence changes. + - A blank bare string, a number, a non-template envelope and the like at `title` or `message` are refused with a sentence that names the key, prescribes `'{record.name}'` or `{ dialect: 'template', source: '{record.name}' }`, and says why: the notify executor interpolates single-brace `{token}` placeholders, and a doubled brace keeps its outer braces in the sent text. The branch issue that `formatZodIssue` and the API error mapper print beneath it carries the same sentence, so no `{{…}}` prescription reaches the author on these two keys. The build's flow judge (`FlowSchema`, flow registration, `os validate`) quotes the new sentence. + - Every other template slot keeps its sentence, which still prescribes `'{{record.name}}'`. That covers `titleFormat`, the prompt template's `system` / `user`, and any slot typed `TemplateExpressionInputSchema`. `TYPED_EXPRESSION_SOURCE_REQUIRED.template` and `TYPED_EXPRESSION_DIALECT_ONLY.template` are unchanged. + - The `tmpl` docblock no longer calls the envelope "Mustache" or shows only `{{record.x}}`. It now says which renderers read which braces: `{{record.x}}` for the formula template engine and the messaging, email and i18n renderers, `{record.x}` for a notify node's `title` / `message`, and either for `titleFormat`. The `TemplateExpressionInputSchema` docblock lists the notify slots the same way, and the generated expression reference page says a template slot's fix is written in the spelling its renderer reads. + - No export is added, removed or renamed, and no type changes. The notify slots take the same input as `TemplateExpressionInputSchema` from a constructor that stays internal to the package. +- 51290bc: Form help and refusals an author reads no longer carry service-interface names, ruling dates or another product's ids + + Clause-②: no + + Wording only: no schema, key, type, export or error-code change. + + - The notify node's Template help (the `NotifyConfigSchema.template` describe and the Studio + inspector's copy in `@objectstack/service-automation`) names the deployment's default locale in + product words instead of `II18nService.getDefaultLocale()`, and drops its ruling date. + - `MANIFEST_ID_EXAMPLES` is now `com.acme.crm` and `org.example.help-desk` (was `com.steedos.crm` + and `org.apache.superset`). The package-id refusal opens with the headline + "Invalid package id 'VALUE'." and names the key in the sentence after it, so the headline alone + carries no JSON path; the rule, the examples and the suggestion follow unchanged in substance. A + caller that matched the old "on KEY. Expected reverse-domain notation" wording matches the + headline, or compares against `manifestIdRefusal()` by reference, instead. + - Ruling dates leave the describes Studio renders as form help: field `required` and `multiple`, + form-view field and section `visibleWhen`, section `collapsible` / `collapsed`, the redirect + arm's `submitBehavior.url`, and page `kind` / `source` (the ADR citations stay). They also + leave the refusals for padded grouping field names, `submitBehavior.url`, `features.*` in a + form-view predicate, and the four filter comparand refusals (null ordering comparand, + `{ $field }` in a list position, null list member, blank `$between` bound). Each sentence still + states the rule, why it exists and the repair. + - The email-template form's Identity section help says how senders address a template instead of + naming `IEmailService.sendTemplate`, in all four shipped locales. + - The action `description` help no longer ends in a dangling dash left behind by an earlier + strip: "(one dialog, not two —)" now reads "(one dialog, not two)". +- 299a2c6: The spec's objectui citations, and the shipped description text that names the `.objectui-sha` pin (the `FormField.span` describe and six migration-entry descriptions), are re-measured against the new console pin, objectui `a58626c88dc8`. + + Clause-②: no + + Every anchor was mapped through the objectui diff `0abd4f9f8769..a58626c88dc8`, 252 paths over 36 commits. Fifteen of those paths are files an asserting record cites: `ObjectKanban.tsx`, `KanbanImpl.tsx`, `ObjectTree.tsx`, `ObjectTimeline.tsx`, `record-details.tsx`, `action-group.tsx`, `action-menu.tsx`, `static-params.ts`, `MetricWidget.tsx`, `MetricCard.tsx`, `form.tsx`, `plugin-kanban.mdx` and the `en` / `zh` / `de` packs. In each, every cited line is byte-identical at the new pin, so each anchor that moved was re-pointed to its new line and the record says by how much; no read point an asserting record cites changed content or died. Every other cited file is byte-identical across the hop (`git diff --quiet`). The seven quoted anchor lines verify against objectui at the new pin. Three records carry a count, and each count was re-taken by its record's own method with the same reading: the `keyboardNavigation` hit lines (15, against 3 for the `schema.editable` control), `ObjectKanban.tsx`'s `quickAdd` / `onQuickAdd` (2 each, against 11 for `onCardClick`), and the `ElementDataSourceGate` occurrences in five `src/index.tsx` shells (0, 3, 3, 3 and 4). + + The six migration entries' corpus counts were re-taken with `git grep -o -F`, the method that first reproduced every `0abd4f9f8769` number. The corpus is now 7754 tracked files. All 99 checked tokens (the export lists of `plugin-lifecycle-advanced.zod.ts`, `tracing.zod.ts` and `metrics.zod.ts`, plus every named key) still read zero, except `Span` / `SpanSchema`, which read 509 / 57: the one new `Span` hit is a `colSpan`. + + No key, default, enum member or export moves. + ## 17.7.0 ### Minor Changes diff --git a/packages/spec/package.json b/packages/spec/package.json index 509499f26c6..833825426cf 100644 --- a/packages/spec/package.json +++ b/packages/spec/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/spec", - "version": "17.7.0", + "version": "17.8.0", "description": "ObjectStack Protocol & Specification - TypeScript Interfaces, JSON Schemas, and Convention Configurations", "license": "Apache-2.0", "main": "dist/index.js", diff --git a/packages/triggers/trigger-api/CHANGELOG.md b/packages/triggers/trigger-api/CHANGELOG.md index e1ec6b43ea0..da61cdce77e 100644 --- a/packages/triggers/trigger-api/CHANGELOG.md +++ b/packages/triggers/trigger-api/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/trigger-api +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/triggers/trigger-api/package.json b/packages/triggers/trigger-api/package.json index 4c2e1f4f6e8..1a9cc4b6df2 100644 --- a/packages/triggers/trigger-api/package.json +++ b/packages/triggers/trigger-api/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-api", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Inbound HTTP/webhook flow trigger for ObjectStack — per-flow HMAC-verified endpoints with queue-backed ingestion (ADR-0041)", "main": "dist/index.js", diff --git a/packages/triggers/trigger-record-change/CHANGELOG.md b/packages/triggers/trigger-record-change/CHANGELOG.md index 2143e70430d..b8369885f5e 100644 --- a/packages/triggers/trigger-record-change/CHANGELOG.md +++ b/packages/triggers/trigger-record-change/CHANGELOG.md @@ -1,5 +1,30 @@ # @objectstack/plugin-trigger-record-change +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/triggers/trigger-record-change/package.json b/packages/triggers/trigger-record-change/package.json index c949f625626..539a640c5ee 100644 --- a/packages/triggers/trigger-record-change/package.json +++ b/packages/triggers/trigger-record-change/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-record-change", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Record-change flow trigger for ObjectStack — auto-launches flows on object insert/update/delete via ObjectQL lifecycle hooks (ADR-0018)", "main": "dist/index.js", diff --git a/packages/triggers/trigger-schedule/CHANGELOG.md b/packages/triggers/trigger-schedule/CHANGELOG.md index 5e3bf460d9a..5c94e47c2a1 100644 --- a/packages/triggers/trigger-schedule/CHANGELOG.md +++ b/packages/triggers/trigger-schedule/CHANGELOG.md @@ -1,5 +1,33 @@ # @objectstack/plugin-trigger-schedule +## 17.8.0 + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/metadata-core@17.8.0 + - @objectstack/types@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/triggers/trigger-schedule/package.json b/packages/triggers/trigger-schedule/package.json index 30182b6137d..d9677934255 100644 --- a/packages/triggers/trigger-schedule/package.json +++ b/packages/triggers/trigger-schedule/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/trigger-schedule", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Schedule flow trigger for ObjectStack \u2014 auto-launches flows on a cron/interval/once schedule via the IJobService (ADR-0018)", "main": "dist/index.js", diff --git a/packages/types/CHANGELOG.md b/packages/types/CHANGELOG.md index 3b488d8d6a9..3e9530826a8 100644 --- a/packages/types/CHANGELOG.md +++ b/packages/types/CHANGELOG.md @@ -1,5 +1,41 @@ # @objectstack/types +## 17.8.0 + +### Minor Changes + +- 5cfd866: feat(sharing): a share-link password can be sent in the `X-Share-Password` header whatever its characters, under a declared encoding (`X-Share-Password-Encoding: utf-8`) + + Clause-②: yes (widening) + + - **What was missing.** A browser cannot put a character above U+00FF in a request header (`Headers` throws a `TypeError` before the request leaves), and it strips leading and trailing spaces. `createLink` accepts any password, so a link whose password has a CJK character or an emoji could not be opened through the header. + - **What is now accepted.** A new companion request header, `X-Share-Password-Encoding`, declares how `X-Share-Password` is encoded. Its one value is `utf-8`, compared case-insensitively. Under it, `X-Share-Password` carries the password's UTF-8 bytes percent-encoded, as `encodeURIComponent(password)` produces them, and both public routes (`GET /api/v1/share-links/:token/resolve` and `/:token/messages`) decode it on both mounts: the sharing plugin's routes and the runtime dispatcher's `/share-links` domain. Both read the pair through one helper, `readSharePasswordHeader`, exported from `@objectstack/types` with the header-name constants. + - **Unchanged.** Without `X-Share-Password-Encoding`, `X-Share-Password` is read raw, exactly as before, so every value a client sends today resolves as it did. That includes a Latin-1 password and a raw password containing `%`; the server never percent-decodes a value nobody declared encoded. The `?password=` query parameter is still read first, and when it is present the header pair is not read. + - **What is refused.** `X-Share-Password-Encoding` naming any other value, or a password header that is not percent-encoded UTF-8 under `utf-8` (a `%` without two hex digits, octets that are not well-formed UTF-8, a character outside visible ASCII), answers `400 VALIDATION_FAILED` before the token is looked up. It is never compared raw instead. The message names the headers and the rule, never the presented value. + - **Response headers.** Both public routes now answer `Vary: X-Share-Password, X-Share-Password-Encoding`, still beside `Cache-Control: no-store`. + - **Cross-origin clients.** `X-Share-Password-Encoding` is in the default CORS preflight allow-list (`DEFAULT_CORS_ALLOW_HEADERS` in `@objectstack/plugin-hono-server`, which the `@objectstack/hono` adapter also applies). A deployment that passes its own `allowHeaders` must add `X-Share-Password-Encoding` beside `X-Share-Password` to let a cross-origin client send an encoded password. + +### Patch Changes + +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [ac9f8bd] +- Updated dependencies [04e776b] +- Updated dependencies [a7df552] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [d4680d2] +- Updated dependencies [cdeabec] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/spec@17.8.0 + ## 17.7.0 ### Minor Changes diff --git a/packages/types/package.json b/packages/types/package.json index 0e073172c85..10685ac78b4 100644 --- a/packages/types/package.json +++ b/packages/types/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/types", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Shared interfaces describing the ObjectStack Runtime environment", "main": "dist/index.js", diff --git a/packages/verify/CHANGELOG.md b/packages/verify/CHANGELOG.md index cfdde042f8d..3a7cdf97a41 100644 --- a/packages/verify/CHANGELOG.md +++ b/packages/verify/CHANGELOG.md @@ -1,5 +1,70 @@ # @objectstack/verify +## 17.8.0 + +### Minor Changes + +- aa71c4d: `sys_user_permission_set` gains `permission_set`, the name of the permission set a grant holds, written beside `permission_set_id` (ADR-0131 D4) + + Clause-②: yes (widening) + + - **The column.** `permission_set` is a read-only text column, at most 100 characters, holding the `name` of the `sys_permission_set` row that `permission_set_id` points at. It is readable everywhere the grant row is readable. A grant written before this release has `NULL` here until the backfill stage rewrites it. No reader uses the column yet: the grant is still resolved from `permission_set_id`, which stays until it is dropped in a later major (ADR-0131 D10). + - **The platform writes it, on every write that carries `permission_set_id`, for every caller.** Two `@objectstack/plugin-security` engine hooks (`beforeInsert` and `beforeUpdate` on `sys_user_permission_set`) look up the set by id and store its name. A write that sends only the id, which is how the data door and the Setup forms write, gets the name filled in. + - **A name that names a different set is refused** with `400 VALIDATION_FAILED`, `invalid_value` at `permission_set`. This covers a name that disagrees with the id written beside it, or with the id already stored when only the name is written. For a non-system caller it also covers a name beside an id that names no set this caller's organization can see. A name that agrees is accepted. A cleared name (`null`) is not stored as a clear: the derived name is written back. Before this change the column did not exist, so a write naming it was refused with `400 INVALID_FIELD`. No write that was accepted before is refused now. + - **Every platform grant writer writes both columns:** the organization-admin reconcile and the platform-admin promotion in `@objectstack/plugin-security`, the self-registration grant in `@objectstack/plugin-auth`, and the RLS probe persona in `@objectstack/verify`. + - **Nothing to migrate.** No principal's grants change. To fill the column on grants written by your own code, write the set's name as `permission_set`, or leave it out and the platform fills it in. Do not write any other value there. + +### Patch Changes + +- Updated dependencies [c28f317] +- Updated dependencies [aa71c4d] +- Updated dependencies [1920cf3] +- Updated dependencies [1920cf3] +- Updated dependencies [0af4f66] +- Updated dependencies [9a0401f] +- Updated dependencies [77a94d8] +- Updated dependencies [ac9f8bd] +- Updated dependencies [879bd38] +- Updated dependencies [b88c356] +- Updated dependencies [8a399b2] +- Updated dependencies [04e776b] +- Updated dependencies [1c563af] +- Updated dependencies [1c563af] +- Updated dependencies [a7df552] +- Updated dependencies [1fb274e] +- Updated dependencies [1fb274e] +- Updated dependencies [c565813] +- Updated dependencies [d5a14dd] +- Updated dependencies [f85a83b] +- Updated dependencies [93125ae] +- Updated dependencies [56c8844] +- Updated dependencies [5cfd866] +- Updated dependencies [d4680d2] +- Updated dependencies [e67ba80] +- Updated dependencies [cdeabec] +- Updated dependencies [4935c66] +- Updated dependencies [15ec50e] +- Updated dependencies [db4c45b] +- Updated dependencies [a543e24] +- Updated dependencies [51290bc] +- Updated dependencies [ace0a53] +- Updated dependencies [299a2c6] + - @objectstack/core@17.8.0 + - @objectstack/plugin-security@17.8.0 + - @objectstack/plugin-auth@17.8.0 + - @objectstack/platform-objects@17.8.0 + - @objectstack/service-automation@17.8.0 + - @objectstack/spec@17.8.0 + - @objectstack/objectql@17.8.0 + - @objectstack/service-settings@17.8.0 + - @objectstack/service-datasource@17.8.0 + - @objectstack/plugin-sharing@17.8.0 + - @objectstack/rest@17.8.0 + - @objectstack/runtime@17.8.0 + - @objectstack/service-analytics@17.8.0 + - @objectstack/types@17.8.0 + - @objectstack/plugin-hono-server@17.8.0 + ## 17.7.0 ### Patch Changes diff --git a/packages/verify/package.json b/packages/verify/package.json index f6383e1aa7e..b3d8f941f32 100644 --- a/packages/verify/package.json +++ b/packages/verify/package.json @@ -1,6 +1,6 @@ { "name": "@objectstack/verify", - "version": "17.7.0", + "version": "17.8.0", "license": "Apache-2.0", "description": "Boot any ObjectStack app in-process and verify it through the real HTTP stack — auto-derived CRUD round-trip fidelity plus the cross-owner RLS invariant. Catches runtime regressions that static checks miss.", "type": "module",