From 0e22dbd33c20637ee297a1ad4a3b1a482ba24738 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Jul 2026 13:19:15 +0000 Subject: [PATCH 1/5] =?UTF-8?q?docs(releases):=20v17=20page=20carries=20th?= =?UTF-8?q?e=20rc.0=20=E2=86=92=20rc.1=20window=20=E2=80=94=20backend=20ha?= =?UTF-8?q?lf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The v17 release page stopped at the rc.0 cut. 334 changesets have landed on main since (40 major-class), plus five objectui pin moves, and none of it was in the layer-3 curated page the releases-maintenance playbook makes mandatory for a major. Adds a "Landed since 17.0.0-rc.0" section written for the two audiences the release actually strands: 1. **Metadata-app developers** — what changed in the metadata they author and the protocol they call: the actions route's HTTP contract, list queries that apply-or-fail, the one alias fold across every engine method, `query.having` becoming real, flow-node config enforcement and the ADR-0087 alias graduations, blank hook targets, stored-metadata conversion replay, Studio forms that saved nothing, view-filter operator parity, the temporal storage/bounds campaign, and ADR-0111 sharing authority. 2. **System administrators** — what changes about operating the platform: honest 501s naming the package to install (the fabricated-session auth mock and the RLS-free analytics shim are deleted), `HonoServerPlugin` reduced to a transport adapter, memory-driver persistence back to opt-in, `os migrate` writing nothing before confirmation and refusing a busy database, boot-log visibility, platform-objects infrastructure, cron jobs that finally fire, and the four fail-opens closed this window. The largest post-rc.0 landings (ADR-0110/0104-D2/0113/0114, the #4001 strictness clicks, the seven protocol-17 retirements, the #4212 family, the #3896 sweep) are already documented in place in the sections above — this section names them and points there rather than duplicating, then covers the rest of the window. Also extends the upgrade checklist with the new consumer-facing actions and the References list with this window's ADRs and PRs. Console (objectui) half follows in the next commit. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MevuZ96T3E9yivS8QvghcR --- content/docs/releases/v17.mdx | 494 ++++++++++++++++++++++++++++++++++ 1 file changed, 494 insertions(+) diff --git a/content/docs/releases/v17.mdx b/content/docs/releases/v17.mdx index a555a889c2..18cd54934e 100644 --- a/content/docs/releases/v17.mdx +++ b/content/docs/releases/v17.mdx @@ -1401,6 +1401,435 @@ objectui commits on top of the pin 16.1.0 shipped. `plugin-map` default import is dropped to match), `chalk` 5→6, and `jsdom` 29→30 (dev) — relevant if you build the Console from source. +## Landed since 17.0.0-rc.0 + +These changes are on `main` after the `rc.0` cut and **roll into 17.0.0-rc.1** — +backend from the pending changesets (334 at the time of writing, 40 of them +`major`-class), Console from the objectui pin advancing +`4a4829d0ef39 → 7d9734d5e321` (79 commits, released as **objectui 17.1.0**). +Sourced from each repo's own changesets. + +One reading note: this page documents the whole 17.0.0 train and has been kept +current as the train moved, so the *largest* post-rc.0 landings are already +documented in place in the sections above rather than repeated here: + +- **ADR-0110 action-declaration admission** (#3935) — including its post-rc.0 + revision: the `OS_ALLOW_UNDECLARED_ACTIONS` valve was **removed before 17 + ships**; the refusal has no opt-out. +- **ADR-0104 D2 action params strict by default** (#3438) — the + `OS_ACTION_PARAMS_STRICT_ENABLED` opt-in era ends; + `OS_ALLOW_LAX_ACTION_PARAMS=1` is the escape hatch — plus the D1 evidence + gates (`os migrate value-shapes`, fresh-datastore attestation, released-file + collection behind the verified file migration). +- **ADR-0113** — `required` becomes the write contract; `storage.notNull` owns + the column. +- **ADR-0114** — the closed field-error catalog and the `fieldErrors` → + `fields` rename (#3977). +- **The #4001 unknown-key strictness campaign's later clicks** — permission + sets + flows, RLS / sharing rules / positions, approval configs, hooks + + datasources, and the app shell & navigation tree, all `.strict()` with + self-fixing errors. +- **The seven protocol-17 retirements** — `api.requireAuth` (#3963), the wait + `timeoutMs`/`onTimeout` pair (#4158), `query.joins`/`windowFunctions` + (#4286), the `fields[]` object form (#4196), `query.cursor` / `distinct` + (#4286), `BatchOptions.validateOnly` (#4052) — and #4350's correction + re-labelling them protocol **17** (their tombstones said "18", so + `os migrate meta` was stepping over the two stack conversions and the + generated upgrade guide omitted all seven). +- **The #4212 retirement family** — the plugin lifecycle hooks, the typed-event + cluster, `ObjectQLEngine.use()`, the Dev Mode Plugin Protocol (#4149). +- **The #3896 security sweep's enforcement half** — criteria-less sharing + rules share nothing, RLS `enabled` is enforced, RLS `priority` and the four + inert tool keys are removed. +- **Flow `errorHandling.maxRetries` has one default** (#4247), **per-run flow + summaries** (#4354), and **file-backed SQLite in WAL mode** (#3941). + +What follows is the rest of the window: first the metadata- and +protocol-facing changes an upgrading application developer must read, then the +platform capabilities an administrator gains, then the Console delta. + +### Breaking / behavior — protocol & wire + +- **The actions route speaks HTTP (#3962, #3951, #3937, #3913).** The + accidental 200-with-inner-envelope double wrap is gone; the contract is now + identical to `/data`: ran-and-returned = **200** `{success: true, data: + }`, single wrap; ran-and-rejected (business rule / + validation) = **400** with `error.details.code: 'VALIDATION_FAILED' | + 'FLOW_FAILED'` and per-field `fields[]`; not-dispatched = a real 404/403/503 + (an unknown action used to come back `{"success":true,"data":{"success": + false}}`); crashed (`TypeError`, driver class, sandbox timeout) = **500**, + visible to gateway error rates and APM instead of arriving as a green 200. + Five defects traced to the extra layer, including the Console's green toast + on failed actions and `redirectUrl` never firing. Object-less actions + canonically key on `'global'` and the `POST /actions//:action` shape is + mounted. `client.actions.*` callers need no change — the SDK still never + throws, and folds every failure into `{success: false, error}`. +- **`POST /actions/:object/:action` dispatches on the declared `type` + (#3915).** A `flow` action executes via the automation service **as the + caller** — `userId` / positions / permissions / tenant forwarded, so a + `runAs: 'user'` flow enforces RLS as the invoker instead of the user-less + UNSCOPED path — and the params bag is seeded with `recordId` (+ the declared + `recordIdParam`); `url`/`modal`/`form`/`api` actions answer 400 naming what + to call instead of a registry miss. Standalone `defineAction` artifacts and + Studio-authored action rows now resolve on REST with their + `requiredPermissions` enforced (previously MCP-only). Script action bodies + run with a bound, attributed `ctx.api` — owner-scoped writes stop dying + `FORBIDDEN`, and body writes are stamped with the caller and tenant and + join the open transaction. +- **A list query either applies or fails (#4121, #4134, #4164, #4181, #4226, + #4254, #4256).** Every input of the list/read surface now either takes + effect or answers 400 — nothing is silently dropped: a `?filter=` that does + not parse (or parses to a non-filter) is `400 INVALID_FILTER` — it used to + return the **unfiltered** table as a clean 200; unknown fields in `sort` / + `select` / `expand` / `searchFields` / `groupBy` / `aggregations` are + `400 INVALID_SORT` / `INVALID_FIELD` (a typo'd `select` returned **every** + column against FLS and data minimisation; an unknown `groupBy` collapsed N + groups into one; `sum()` folded to `0` — indistinguishable from a real + zero in a report); an unknown bare query parameter is `400 INVALID_FIELD` + with the canonical suggestion (`?pageSize=5` used to become a zero-matching + filter answering `total: 0`); a dotted-path sort (`?sort=account.name`) is + rejected with the denormalisation prescription; explicit `filter` + bare + field parameters compose with `$and` instead of dropping the implicit half; + and two sort spellings that silently never applied — the SDK's declared + `orderBy: string[]` and the `{field: direction}` map — now actually sort. + Export honours the searched view (`search=` / `searchFields=`, #4181). +- **Unsorted paged reads are deterministic (#4363, #3821).** A `limit`/`offset` + read with no `orderBy` — the shape every list view without a configured sort + sends — is now ordered by the unique key on SQL and MongoDB, and any + non-empty `orderBy` gains a unique tie-breaker, so page 2 can no longer + repeat a row while another row is never served. +- **Query options fold once, everywhere (#3795, #4346, #4371).** The five RPC + aliases (`filter`→`where`, `select`→`fields`, `sort`→`orderBy`, + `skip`→`offset`, `populate`→`expand`) resolve through one spec-owned fold + with one precedence — four of five pairs were inverted between readers, so + `?select=a&fields=b` answered `[a]` on one path and `[b]` on the other. The + fold now covers **every** engine method: `findOne` / `count` / `update` / + `delete` / `aggregate` with `{filter}` used to match **every row** — + `update(data, {filter, multi: true})` rewrote the whole table and + `delete({filter, multi: true})` emptied it. Conflicting spellings + (`{where: X, filter: Y}`, `{top: 1, limit: 3}`) are refused naming both; + direct engine callers passing wire-only spellings or unknown option keys now + get an error naming the canonical key instead of silence (#4371) — which + also fixed queue purge, which passed a key the engine never read and so + deleted nothing. +- **`query.having` is enforced (#4286 follow-up).** Declared since AST v2 and + executed by nothing, `having` now filters aggregated rows on both the + native-driver path and the in-memory fallback, with `$and`/`$or`/`$not` and + loud rejection of unknown operators. A query that carried it was silently + returning every group. +- **Request bodies validate at the entry (#3899, #3878).** Catalog-declared + `requestSchema`s are enforced with `400 VALIDATION_FAILED` + `fields[]`: a + garbage `POST /data/:object/query` body used to degrade into an unfiltered + full read, `POST /automation/:name/toggle {"enable": false}` used to + **enable** the flow and answer 200, and a misnamed `POST /notifications/read` + key was a 200 no-op. `/analytics/query` and `/analytics/sql` validate the + **bare `AnalyticsQuery`** at the entry — the `{cube, query, format}` + envelope dialect of the retired shim is tombstoned, and the filter field is + `where`, not `filters` — instead of dying downstream as a 500 SQL syntax + error. +- **Bulk writes are bounded and bound to the path (#3939, #3933, #3897, + #3946).** The declared 200-row batch cap is now real on all five bulk routes + (`400 BATCH_TOO_LARGE`, governed by `batch.maxBatchSize`, 1..1000); a body + `object` can no longer move a bulk write or a `/data/:object/query` read to + a different object than the gated URL object; `deleteMany` builds its + predicate from the validated id list only — caller `options.where` used to + widen a one-id request into deleting everything the caller could see — and + per-id deletes now run `deleteBehavior` cascades and RLS/FLS under the + caller (its response becomes the structured `BatchUpdateResponse`); and a + caller-supplied `context` is dropped at ingress on these paths (#3960 — it + could previously become the execution context, `{isSystem: true}` included, + where no server context resolved). +- **Response envelopes converge (#3843, #3983, #4038, #4053, #4224).** The + settings, datasource-admin, external-datasource, package and share-link + route modules emit the declared envelope — payload under `data`, errors as + structured `{code, message}` on the ADR-0112 ledger — which un-broke two + shipped SDK methods (`client.shareLinks.create()` / `.list()`); the + dispatcher's duplicate top-level payload keys are gone; `GET /ai/agents` + (the last unenveloped SDK route) conforms; `/api/settings/*` error extras + move under `error.details`, with `SETTINGS_VALIDATION.fields` becoming + ADR-0114 `FieldError[]`. Raw-`fetch` callers of these routes add one `.data` + hop and read `error.code`; SDK callers are shielded — and the client now + normalizes both server envelope families, so `err.code` is always the + semantic string, never the HTTP status number (#3918). +- **Field validation speaks the caller's language (#3957).** Built-in + field-validation messages render in the request's locale (en, zh-CN, ja-JP, + es-ES — `Accept-Language` / `?locale`, falling back to the workspace + locale), name the field by its translated label, and carry a structured + `constraint` (`min`/`max`/`actual`/…). Match on `code` and `constraint`, + not English message text; any built-in is overridable per deployment via + `validation.field.` translation keys. +- **An absent capability answers honestly (#4093, #4113, #3891, #4087, + ADR-0115).** The last fabricating fallbacks are deleted. A mounted domain + with no implementation answers **501 naming the package to install** + (`/automation`, `/notifications`, `/ui/*`, `/ai/*` — previously misleading + 404/503s that sent operators chasing phantom routing bugs). `/auth/*` + without an auth service answers 501 instead of a **200 carrying a fabricated + session for any email and any password** — the mock shipped in production + `@objectstack/runtime` and told clients the one thing a server must never + lie about. The degraded analytics shim — which dropped the caller's + ExecutionContext, so authenticated callers got **aggregates over rows RLS + would hide**, and read a non-contract `filters` key — is removed: without + `@objectstack/service-analytics` the routes are not mounted (404), and + discovery reports `analytics: unavailable` instead of hardcoding it always-on + (#3989, #4010). The dispatcher's dead `/storage` bridge (which could never + complete a request, and whose wildcard shadowed the real presigned + `service-storage` routes) is removed. DevPlugin's stub table is retired — + an empty slot in dev behaves exactly as in production, and the allow-all + permissions / no-row-filter RLS / unmasked-field dev fakes now **fail + closed** like production does. Discovery computes every slot's verdict from + the registered service's own `__serviceInfo` (the `_dev: true` marker is + retired), its remedy strings name real packages (ten of fifteen previously + named packages that do not exist), and a slot self-declaring + `handlerReady: false` is answered like an empty slot instead of having its + fabricated 200 served (#4058, #4089, #4130). +- **`HonoServerPlugin` is a transport adapter (#4073).** Its raw data-C+R + surface, its third (pre-`DiscoverySchema`) discovery payload and the + `registerStandardEndpoints` flag are deleted — data and discovery each have + exactly one owner (`@objectstack/rest`; the runtime dispatcher). Every + composed host (`os serve`, `objectstack dev`, cloud) already answered + byte-identically. The three current-user endpoints (`/auth/me/permissions`, + `/auth/me/localization`, `/me/apps`) register unconditionally — no longer + hostage to the flag — yield correctly around the auth wildcard in either + registration order (#4088, #4117), and resolve per request through the + multi-tenant `kernel-resolver` seam, so authenticated tenant callers on a + routing host stop being told `{authenticated: false}` (cloud#927). +- **The memory driver is pure in-memory again (#4065, #4083).** A bare + `new InMemoryDriver()` — and a declared `driver: 'memory'` datasource — + silently wrote `.objectstack/data/memory-driver.json` into the working + directory and reloaded it on the next boot; the `persistence: 'auto'` + default drifted from the accepted opt-in design (#815) and is now `false`. + Durability is explicit (`{ persistence: 'file' }`, per-datasource + destinations), and the driver's "production-ready" claim is trimmed: it + stores no constraints — use in-memory SQLite when constraints matter. + `os init` scaffolds stop naming `driver-memory` as a dependency. +- **The `artifact-api` metadata source is removed (#4246).** Zero consumers + existed and half its URL contract had been dead since v5.0. + `{ mode: 'local-file', path }` is the single artifact source (it accepts the + public/commit-pinned cloud URLs); a still-configured `artifact-api` throws + loudly at `start()` instead of silently falling through to filesystem + scanning. +- **Smaller wire corrections:** the plural `/meta/:type` spellings no longer + skip the book-audience / app-RBAC / dashboard gates the singular spellings + enforced (#3984); `book.audience: 'public'` genuinely serves anonymously on + secure deployments (#3963 step 2) — review books that declare it, they are + now really public; three orphan operator vocabularies with zero importers + leave the spec (objectui#2945 Track A); `DriverPlugin`'s inert + `{datasourceName, registerAsDefault}` options are retired (#4320); the + legacy `_dev: true` service marker is retired in favour of `__serviceInfo`; + and exported spec types stop resolving to `any` (`NavigationItem`, + `FormField`, `QueryAST['fields']`, the `z.input` sides of the recursive + schemas) — authoring code whose invalid shapes previously compiled clean + now fails `tsc` with the real error, gated by the new `check:exported-any` + (#4171, #4195, #4221). + +### Breaking / behavior — metadata authoring & runtime + +- **Flow-node config is enforced end to end (#4277, #4045, #4027, #4347, + #4389).** The twelve contract-carrying builtins `parse()` their `config` + before executing — a violation refuses the node as a guard naming every + violated path; `registerFlow` **rejects** undeclared config keys (exact + path, declared key set, did-you-mean, per-key tombstones like + `screen.visibleIf` → `visibleWhen`) and warns on keys a `configSchema` does + not declare; declared bare-CEL slots are validated + (`screen.fields[].visibleWhen` authored as `'{var} == true'` shipped a + forever-paused run); and ADR-0031 regions (`loop.body`, + `parallel.branches[]`, `try_catch.try/catch`) are walked by conversions, + validators and lint alike, so nested nodes stop escaping every check. A + legacy `{var}` condition carrying an unresolved dotted reference now + refuses instead of comparing strings — `'oppRecord.amount > 500000'` was + constantly true: a gate that never gated. +- **Flow-node aliases graduate to conversions (ADR-0087 D2).** `subflow` and + `map` `config.flow` → `flowName` (#4278, #4045), `notify`'s nested + `source: {object, id}` → `sourceObject`/`sourceId`, and `connector_action`'s + mis-rooted config lifts onto `connectorConfig` — Studio-authored connector + nodes never dispatched; stored flows are healed at load. All protocol-17 + windows applied by `os migrate meta` and at every rehydration seam, + alongside the seven #3796 aliases documented above. +- **A blank hook target is refused.** `object: ''` / `[]` silently registered + the hook on **every object in the tenant**; a wildcard must now be the + visible `'*'`, and `bindHooksToEngine`'s `strict` option actually fails + fast. `defineHook()` lands so convention-scanned hook files get the same + parse-at-import treatment as the rest of the define family (#4269). +- **Stored metadata replays the conversion chain (#3903).** Studio- and + API-written rows at rest go through the same ADR-0087 machinery as authored + source at every read seam (including `registerFlow` rehydration) — a stored + action with the removed `execute` dispatches via `target` again; boot + hydration validates each row post-conversion and diagnoses invalid ones + with `[metadata_spec_invalid]` instead of shrugging. +- **Studio's metadata forms tell the truth (#3786).** Four of seventeen forms + had drifted from their schemas, so controls saved nothing: the Object → + Capabilities toggles bound a key the schema does not declare (all seven + dead — Track history, Searchable, API enabled, Files, Feeds, Activities, + Clone), and the Fields grid offered sixteen undeclared keys (PII, Encrypted, + Indexed, …) while missing the canonical spellings for five renames. Forms + are reconciled and gated registry-wide; re-author anything configured + through the dead controls. +- **Authored view filters compile (#3948, #4029).** Eight canonical + `VIEW_FILTER_OPERATORS` members (`equals`, `before`, `after`, …) that + validation accepted but the AST refused now lower to real queries on every + driver, and an uncompilable filter element **throws** instead of matching + every row. `saveMeta` persists canonical operator spellings, so the ~30-entry + legacy alias bridge stops growing and can eventually retire + (objectui#2945). +- **Analytics stops guessing (#4157, #4128, #4033).** An undeclared measure or + an out-of-vocabulary measure `type` **errors** instead of silently answering + `COUNT(*)` (revenue charts that were quietly row counts now say so); the + silently-dropped filter family — `$between`, `$null`, `$startsWith` / + `$endsWith`, inverted `$exists: false`, `$notContains`, and the `$or`/`$not` + combinators — now actually filters, so dashboards stop drawing every row; + time-bucketed queries project their bucket column (a trend chart got N + values and no x-axis). Time-typed dimensions gain chronological default + order, and `ReportSchema.order` becomes a declarable ordering lowered into + the dataset selection (#3916). +- **Temporal correctness lands as one campaign (ADR-0053).** `Field.datetime` + and `Field.time` get one canonical storage form per backend — mixed + epoch/ISO storage made a two-sided date window return **zero rows** on + SQLite while matches existed, and MySQL moves to `DATETIME(3)` with + UTC-pinned connections (REST datetime writes on MySQL previously failed + outright); legacy columns converge at schema sync and `os migrate plan` + lists the in-place work with row counts (#3912, #3994, #4047, #3954). A + bare `YYYY-MM-DD` upper bound covers its whole calendar day across SQL / + memory / MongoDB / the RLS write-side evaluator — dashboards lost the final + day's rows after midnight, and a `{ $lte: '{today}' }` RLS `check` policy + denied every write made after 00:00 (#3777, #4042). The type-blind + evaluators compare a JS `Date` against wire text correctly — fail-closed + RLS was denying legitimate SDK writes whose payload carried `new Date()` + (#4191). `date` `NOW()` defaults resolve UTC on every dialect (#4022). A + shared conformance matrix in `@objectstack/spec/data` is asserted by five + backends so none of these classes can silently re-drift. +- **Sharing authority is real authority (ADR-0111, #3902).** The + share-management surface authenticated the caller and then ran under + `SYSTEM_CTX` — **any signed-in user could revoke anyone's share, enumerate + who-sees-what, write self-grants, and define org-wide sharing rules**. Now: + `canManageShares` (system, record owner, Modify All Data, or hierarchy + write-scope depth) is enforced in the service on every + `/data/:object/:id/shares` verb; `listShares` is management-gated and the + open `sys_record_share` read surface is self-scoped for non-admins; the + whole `/sharing/rules` surface requires the new **`manage_sharing`** + capability (seeded into `admin_full_access`); an `edit`-level share no + longer confers **delete** (`canDelete` = ownership, write depth, or + `modifyAllRecords`; bulk deletes stop widening through shares); revoke + validates the share belongs to the URL's record; grants on objects the + sharing gates never consult fail `SHARING_NOT_ENABLED` instead of sitting + inert; and a record's owner or a Modify-All admin — not just the minter — + can revoke a share link. + +### Security corrections + +Beyond the sharing-authority work and the #3896 enforcement half documented +above, four fail-opens were found and closed in this window — all ship in +rc.1, and administrators should know what changed: + +- **The project-membership gate never ran (#4127 sweep).** + `enforceProjectMembership` probed the auth service through a shape it never + had, so `userId` stayed unset and a **signed-in non-member passed the + gate** on every deployment with project scoping on. Found by the new typed + service-slot lint; the lint stays on to keep the class closed. +- **Analytics answered without a caller (#3891).** The degraded shim served + RLS-free, tenant-unscoped aggregates (removed — see above). +- **Dev stubs inverted their decisions (ADR-0115, #4093).** With + `plugin-security` absent, dev filled the slots with allow-all / no-RLS / + unmasked fakes behind one warn line; the slots now stay empty and fail + closed, and `plugin-dev` refuses `NODE_ENV=production` outright + (`OS_ALLOW_DEV_PLUGIN=1` overrides, and now brands the boot loudly instead + of starting silent, #3900). +- **Imported users skipped field coercion (#4251 sweep).** The + `/admin/import-users` route probed a method its service never had, so rows + reached `sys_user` uncoerced on every deployment. The same typed-slot + campaign fixed `handleAuth` reaching the (now deleted) mock instead of the + real auth service, and `/analytics/sql` crashing 500 on providers without + `generateSql`. + +### New capabilities (backend) + +- **Approvals grow up as a decision surface.** A `decisionOutputs` entry can + declare `required: true` — an approve carrying a blank routing value is + refused before any write, instead of the next node faulting or the run + stalling (objectui#2955); a reassign writes structured `reassign_from` / + `reassign_to` parties that timelines render without free-text archaeology + (#4365); and a `runAs: 'system'` flow's writes are audited as + `svc:flow:` instead of "Unknown user" (#4366). +- **Run summaries learn honesty about side effects (#4395, #4396).** + `connector_action` and mutating `http` nodes report `unmeasuredEffect` (the + platform cannot know whether `crm.push_opportunity` writes), an + `unmeasured_count` column joins `sys_automation_run`, and the broken-sweep + detector becomes `selected > 0 AND acted = 0 AND unmeasured = 0` — without + the third clause it would fire on every healthy connector-driven flow. +- **Scheduled jobs actually fire.** Cron jobs registered before + `kernel:ready` landed on a placeholder adapter that silently ignores `cron` + schedules — in the default configuration a business plugin's cron jobs + never ran. Early registrations now migrate onto the DB-backed adapter, so + operators will observe scheduled work starting to happen. +- **`os migrate` keeps its promises (#3917, #3924, #3954, #3955, #3978).** + Zero database writes before the confirmation prompt (boot-time DDL is + deferred and rendered in the plan); a busy SQLite database is detected — + file-descriptor probe naming the holding pid, which works on the default + rollback-journal mode — and refused without `--force`; the plan lists + in-place datetime/time normalisation with row counts so large rewrites can + be scheduled into maintenance windows; zh-locale deployments stop being told + to drop their live pinyin `__search` companion columns as "destructive + orphans"; and virtual formula fields stop appearing as forever-pending work. +- **Boot tells operators the truth (#4012, #4085, #4095, #4110, #4096, #4167, + #4002).** Plugin boot-phase warnings survive the startup banner (previously + discarded wholesale — including the ADR-0110 action-governance inventory); + `os serve ` boots a fresh project with no compiled artifact instead + of dying with a misleading error, and grafts the config module's + `onEnable`/`functions` onto artifact boots so declared `script` actions stop + 404ing; a **named** artifact path that does not exist fails the boot naming + the path — it used to print "Server is ready" over an empty platform; + `OS_STORAGE_ROOT` actually takes effect (uploads land under + `.objectstack/data/uploads` from the first byte) and the spurious every-boot + "storage adapter swapped" warning fires only on a real move; `stack.storage` + — never a declared key, silently ignored — is linted with the two working + channels named (`OS_STORAGE_*`, Setup → Settings); and authored `api.*` + keys survive the serve-time config merge instead of being replaced + wholesale. +- **Platform infrastructure is composable (#4243, #4270, ADR-0116).** + `sys_migration` and `sys_secret` move to `PlatformObjectsPlugin`, so a + kernel without storage or settings keeps its migration ledger and its + encrypted-secret store — `Field.secret()` writes previously threw on + settings-less kernels. The kernel `Plugin` contract gains declared ordering + (`optionalDependencies`, `requiresServices`, `providesServices`) with + boot-time validation that names both plugins, both slots, and the fix; + `PLATFORM_ALWAYS_ON_CAPABILITIES` is a published export, so hosts stop + hand-mirroring the always-on slate (cloud's mirror had silently lost `sms`, + `messaging` and `analytics`). Graceful shutdown disconnects datasource + pools through the one datasource path, and adopted pools are never closed + by a kernel that does not own them (#3993). +- **Seeds load what you wrote (#3911, #3932).** Natural-key **arrays** resolve + for `multiple: true` lookups (previously dropped with a warn), and dropped + unusable references are counted and named in the boot banner + (`showcase 42 ok / 3 lost links`) instead of the load reading clean. +- **Authoring lints gather the evidence (#3786, #4120, #4271, #4254, #3991).** + The advisory `lintUnknownAuthoringKeys` reports every silently-stripped + object/field key across all sixteen metadata collections — with + rename/retirement guidance and nested-metadata descent — through + `defineStack`, `os validate` and `os compile`; error-severity + reference-integrity rules gate flow `update_record` / `create_record` + writes to undeclared fields and stale `searchableFields` declarations; + hook and action bodies get the same write-set analysis as advisories, plus + a runtime report for dead `ctx.record` writes (#4345); and a field carrying + both per-tenant `unique: true` and a global unique index is flagged before + the second tenant finds out. +- **Service slots are typed contracts (#4251, #4127).** `IObjectQLEngine` + lands (the seven consumer-local stand-in surfaces die), `getService` + resolves through a slot→contract ledger, and a shrinking baseline bans + `any`-typed lookups. This campaign is what surfaced the membership + fail-open, the import-coercion miss and the cron-adapter migration above — + the ratchet keeps the class closed. +- **Declared UI slots become authorable.** `FormSection.pane` places sections + in split forms explicitly (objectui#2153), and `element:button`'s + `properties.action` gains `InlineActionSchema` — previously authorable only + via `as any` (objectui#2997). +- **Package hygiene reaches consumers (#4261, #4248, #4097).** `CHANGELOG.md` + ships in every tarball again — 68 of 69 packages had silently severed the + delivery path for exactly the FROM→TO migration notes an upgrading agent + greps after a tombstone error; twenty packages stop shipping raw sources + and tests to npm; `os init` stamps `engines: { protocol: '^17' }` so new + packages join the ADR-0087 load-time handshake; and `spec-changes.json`, + the generated upgrade guide and the `spec_changes` MCP tool actually + report the 16 → 17 chain (they still said 16.0.0). + ## Upgrade checklist ### 17.0.0 @@ -1500,6 +1929,52 @@ objectui commits on top of the pin 16.1.0 shipped. to `isolated`. - **Approvals readers:** anything that listed requests tenant-wide must now be a participant or hold the admin override. +- **Raw-HTTP action callers:** branch on the HTTP status — non-2xx is the + failure, and a 200's `data` is the handler's return directly, one wrap less + (#3962). Crashes are 500s now; treat them as server faults. SDK callers + need no change. +- **List-query callers:** malformed filters and unknown + `sort`/`select`/`expand`/`groupBy`/`aggregations`/`searchFields` names now + answer 400 instead of silently over- or under-returning — fix the request + the error names, and note that previously-ignored `orderBy: string[]` and + `{field: direction}` sorts now actually apply. +- **Raw-`fetch` readers** of the settings / datasource-admin / + external-datasource / package / share-link routes: read the payload under + `data` and errors as `error.code` / `error.message` (#3843, #3983). +- **Bulk callers:** stay under `batch.maxBatchSize` (default 200, raisable to + 1000) or chunk; stop sending `object` / `context` in bulk bodies (ignored); + read `deleteMany`'s new structured `BatchUpdateResponse` (#3939, #3897). +- **Analytics clients:** send the bare `AnalyticsQuery` shape (`where`, not + `filters`; no `{cube, query, format}` envelope) (#3878). Embedded / + programmatic hosts must install `@objectstack/service-analytics` — the + context-less fallback is gone (#3891). +- **Bare `HonoServerPlugin` hosts:** mount `@objectstack/rest` or the runtime + dispatcher for data/discovery — the convenience surface and its flag are + deleted (#4073). +- **Direct `new InMemoryDriver()` / `driver: 'memory'` users:** pass + `{ persistence: 'file' }` if you relied on the accidental durability, and + delete stale `.objectstack/data/memory-driver.json` files (#4065, #4083). +- **`artifact-api` sources:** switch to the `local-file` URL form or + `os package install` — a configured `artifact-api` now fails the boot + (#4246). +- **Hand-built kernels:** compose `PlatformObjectsPlugin` if you relied on + storage/settings registering `sys_migration` / `sys_secret` (#4243, #4270); + declare the ADR-0116 ordering fields on plugins that resolve services in + `init()`. +- **SQLite operators:** back up with `sqlite3 app.db ".backup …"` — a bare + file copy can miss committed transactions in the WAL sidecars — and set + `OS_DATABASE_SQLITE_JOURNAL_MODE=delete` on NFS/SMB (#3941). +- **Dev setups:** the fabricating stubs are gone — install the real plugin + for any slot a dev call now finds absent, and note `plugin-dev` refuses + `NODE_ENV=production` without `OS_ALLOW_DEV_PLUGIN=1` (ADR-0115). +- **Sharing admins:** grant the new `manage_sharing` capability to roles that + administer sharing rules, and expect edit-level shares to stop conferring + delete (ADR-0111). +- **Flow authors:** rename aliased node-config keys (`subflow`/`map` + `flow` → `flowName`, notify `to`/`subject`/`body`/`url` → + `recipients`/`title`/`message`/`actionUrl`, script `functionName`/`input` → + `function`/`inputs`) or run `os migrate meta` — the conversion windows + close at protocol 18 (#3796, #4278, #4045). ## References @@ -1516,3 +1991,22 @@ derivation) · #3760 (user-less runs) · #3855 (alias retirement) · #3563/#3587/#3612/#3718 (route ledger + SDK surface) · #2462 (GraphQL removal) · #3741/#3758/#3826 (datasource fail-fast) · #3696 (per-tenant `unique`) · #3676/#3778/#3847 (i18n contract conformance). + +Landed since rc.0: ADR-0110 (action declaration admission) · ADR-0111 (sharing +authority) · ADR-0112 (error-code vocabulary) · ADR-0113 (`required` split) · +ADR-0114 (field-error catalog) · ADR-0115 (no fabricating fallbacks) · +ADR-0116 (declared plugin ordering) · ADR-0053 (temporal semantics) · +#3962/#3951 (actions speak HTTP) · #3915 (action type dispatch) · +#4121/#4134/#4164/#4181/#4226/#4254/#4256/#4363 (list queries apply or fail) · +#3795/#4346/#4371 (one alias fold) · #3899/#3878 (request-body validation) · +#3939/#3897/#3933/#3946/#3960 (bulk binding + caps) · #3843/#3983/#4038/#4053 +(envelope convergence) · #3957 (localized validation) · #4093/#4113/#3891/#4087 +(honest absence) · #4073 (Hono transport adapter) · #4065/#4083 (memory-driver +persistence) · #4246 (`artifact-api` removal) · #3903 (stored-metadata +conversion replay) · #4277/#4045/#4027/#4347 (flow config enforcement) · +#3948/#4029 (view-filter operator parity) · #4157/#4128 (analytics stops +guessing) · #3916 (report ordering) · #4350 (protocol-17 relabel) · +#4127/#4251 (typed service slots + fail-open fixes) · #3917/#3924 (`os migrate` +occupancy + deferred DDL) · #4243/#4270 (platform-objects infrastructure) · +#4395/#4396 (unmeasured effects) · #4365/#4366 (approval reassign + audit +attribution) · #4261/#4248 (published-files hygiene). From 6e0b6351bfa120d6908e4c9ec797df948abd3e85 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Jul 2026 13:20:40 +0000 Subject: [PATCH 2/5] =?UTF-8?q?docs(releases):=20v17=20page=20carries=20th?= =?UTF-8?q?e=20rc.0=20=E2=86=92=20rc.1=20window=20=E2=80=94=20Console=20ha?= =?UTF-8?q?lf?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The five objectui pin moves since rc.0 (`4a4829d0ef39 → 7d9734d5e321`, 79 commits, released as objectui 17.1.0) had no entry on the curated page. Sourced from the five `console-*` changesets `scripts/bump-objectui.sh` writes on every bump. Leads with the release-critical one: the rc.0 pin predates the ADR-0110 D1 client fix, so the Console it builds still posts `action.target` to the actions route — against a v17 server, which resolves the declaration by `name` and refuses an unresolvable one, every target-bound script action would 404 from the shipped Console. Then the rest grouped by what a user of the Console actually feels: action feedback that stops lying, filters/sorting/export agreeing with the server, safer edits (If-Match + 409 surfacing), per-record bulk actions, approvals and notification surfaces, the Studio designers, types that stop forking the spec, and the resilience fixes. Also scopes the existing Console section to the rc.0 window it documents, so `4a4829d0ef39` no longer reads as the shipping pin. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MevuZ96T3E9yivS8QvghcR --- content/docs/releases/v17.mdx | 74 ++++++++++++++++++++++++++++++++++- 1 file changed, 72 insertions(+), 2 deletions(-) diff --git a/content/docs/releases/v17.mdx b/content/docs/releases/v17.mdx index 18cd54934e..fc82398c41 100644 --- a/content/docs/releases/v17.mdx +++ b/content/docs/releases/v17.mdx @@ -1316,8 +1316,10 @@ most of its rows in silence. ## New in Console (Studio) — bundled objectui 17.0 -The v17 Console window is `cf2d56e32a11 → 4a4829d0ef39` (`.objectui-sha`) — 128 -objectui commits on top of the pin 16.1.0 shipped. +This section covers the window bundled at **`rc.0`**: `cf2d56e32a11 → +4a4829d0ef39` (`.objectui-sha`) — 128 objectui commits on top of the pin +16.1.0 shipped. The pin has since advanced — the rest of the line is under +*New in Console* in the **Landed since 17.0.0-rc.0** section below. ### Files, actions and forms @@ -1830,6 +1832,74 @@ rc.1, and administrators should know what changed: the generated upgrade guide and the `spec_changes` MCP tool actually report the 16 → 17 chain (they still said 16.0.0). +### New in Console — bundled objectui advanced `4a4829d0ef39 → 7d9734d5e321` + +79 objectui commits across five pin moves, released as **objectui 17.1.0**. + +- **The lockstep half of ADR-0110 — release-critical.** The rc.0 pin predates + the client fix, so the Console it built still posted `action.target` to + `/api/v1/actions/:object/:action`. Against a 17 server — which resolves the + declaration by `name` and refuses an unresolvable one — **every + target-bound script action would 404 from the shipped Console**. The + ADR-0066 D4 capability gate is now applied on every action surface, and a + modal action is client-side only (its server fall-through is dropped). +- **Action feedback stops lying.** A failed server action no longer shows the + green success toast, `redirectUrl` finally fires, one source now owns the + `/actions` envelope rule, and the Console reads the single-wrapped + responses the server started sending (#3962). A server rejection that names + fields marks **those** fields in the form; error-code branches survive the + ADR-0112 rename; a 400 from the server no longer reads to the user as + "check your connection"; and settings validation errors render against the + fields that caused them. +- **Filters, sorting and export agree with the server.** Every spec view + operator is bridged onto the filter AST (the client half of the operator + parity above); a view's own filter no longer vanishes when the user adds + one, nor arrives as a predicate on columns that do not exist, nor depends + on whether the query expands a lookup; a column-header sort orders the + whole list rather than the visible page, and a string `$orderby` reaches + the server as a sort instead of a list of character indices; sorting a + lookup column stops ordering by an invisible key; exporting a searched list + downloads the searched rows, not the unsearched superset; and every column + resolver reads one identity key, resolved at ingestion and reported out + loud. +- **Edits are safer.** Form edit saves send `If-Match` and surface 409 + conflicts instead of silently overwriting; a wizard with `allowSkip` no + longer submits past skipped fields; a select no longer wipes itself when + its value outruns its options; numeric and boolean option values survive + selection typed; multi-value lookup is selectable in inline edit and + hydrates through a batched `$in` (showing loading rather than the empty + placeholder). +- **Bulk actions become per-record.** An object-declared bulk action runs over + the selection, `visible` is evaluated per selected record, params render + through the shared form-field widgets (lookup errors get Retry, `sys_user` + params get the PeoplePicker), and a bulk delete clears the row checkboxes. + The `bulkEnabled` derivation is dropped — the spec key is a tombstone. +- **Approvals and notifications.** Decision outputs reach both decision + surfaces; a reassign hand-off reads legibly and `svc:*` audit actors render + as "System"; the Console mounts the notification surfaces, each spec + `displayType` gets its own presentation, and the spec `icon`, config, + position and action variant are read instead of forked or ignored; + Attachments become a peer tab with a live count badge, translated. +- **Studio and the designers.** A page button created in Studio can be given + an action (the `InlineActionSchema` above is its contract); the script + node's form authors what the executor actually runs; a published + `configSchema` can no longer delete a node's sibling-block editors; and the + flow inspector preserves sibling blocks. `SplitForm` honours the new + `FormSection.pane`. +- **Types stop forking the spec.** `Page`/`App`/`Dashboard` validate the + spec's own fields instead of passing them through; navigation metadata + stops losing spec fields the renderer already honours; the `*Validation` + five and five more spec-named symbols are derived rather than copied; and + the form-field boundary between spec and runtime is declared, with a + tripwire against re-importing the wrong one. The SDUI public contract is + curated and guarded against silent drift, with declared inputs for the + `page:*` / `element:*` / `record:*` block families. +- **Resilience and chrome.** A transient `/me/permissions` or + `/me/localization` failure retries instead of stranding the app on its + loading state; a 403 is no longer blamed on the network; ⌘K search is no + longer capped at 8 objects; the chart view gets a label and an icon in the + view switcher; and the developer-voiced default form subtitle is dropped. + ## Upgrade checklist ### 17.0.0 From da4c2c13d4eba526b881ba796f72b95ba27e4156 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Jul 2026 13:21:42 +0000 Subject: [PATCH 3/5] docs(releases): fold the four post-merge changesets into the rc.1 window MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit main advanced while this branch was being written. Three of the four new changesets are consumer-facing and belong on the page: - #4363's second half — the background walks seek instead of counting. The page already carried the single-read half; the walk half is the one with teeth, because `rebuildApproverIndex` deleted index rows for requests its offset-paged scan skipped, and `scanValueShapes` could open the value-shape migration gate on evidence it never fully read. Both land beside the paged- read entry they complete. - `SqlDriver.findOne(object, id)` removal + `bypassTenantAudit` becoming a declared `DriverOptions` member, with the limit the declaration states. - react page props joining the reference-integrity suite, so `os lint` / `os compile` stop passing metadata `os validate` rejects — noted where the authoring lints are described, with the CI consequence called out. The fourth (`driver-conformance-gate`) is a CI gate that releases nothing consumer-visible and is deliberately left off. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MevuZ96T3E9yivS8QvghcR --- content/docs/releases/v17.mdx | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/content/docs/releases/v17.mdx b/content/docs/releases/v17.mdx index fc82398c41..8f88eada72 100644 --- a/content/docs/releases/v17.mdx +++ b/content/docs/releases/v17.mdx @@ -1499,7 +1499,22 @@ platform capabilities an administrator gains, then the Console delta. read with no `orderBy` — the shape every list view without a configured sort sends — is now ordered by the unique key on SQL and MongoDB, and any non-empty `orderBy` gains a unique tie-breaker, so page 2 can no longer - repeat a row while another row is never served. + repeat a row while another row is never served. **The background walks seek + instead of counting** for the same reason: seven scans paged with a growing + `offset` while writing to the rows they were reading, so rows slid past the + cursor unvisited — which meant `rebuildApproverIndex` **deleted** index rows + for requests it skipped (an approver silently dropped from someone's queue), + `verifyFileReferences` reported referenced files as unreferenced, the file + and pinyin backfills left rows unconverted under a run that reported + success, and `scanValueShapes` — which opens the value-shape migration gate + on its evidence — could vouch for rows it never read. +- **`SqlDriver.findOne(object, id)` is removed.** An undeclared bare-id branch + that was on no contract, that nothing outside the driver's own tests used, + and that the other two drivers answered differently. Pass a query. In the + same pass `bypassTenantAudit` becomes a **declared** `DriverOptions` member + (it was live but read through a cast, so no caller was type-checked), with + its limit stated: it silences a diagnostic and must not change which rows a + write touches. - **Query options fold once, everywhere (#3795, #4346, #4371).** The five RPC aliases (`filter`→`where`, `select`→`fields`, `sort`→`orderBy`, `skip`→`offset`, `populate`→`expand`) resolve through one spec-owned fold @@ -1812,7 +1827,12 @@ rc.1, and administrators should know what changed: hook and action bodies get the same write-set analysis as advisories, plus a runtime report for dead `ctx.record` writes (#4345); and a field carrying both per-tenant `unique: true` and a global unique index is flagged before - the second tenant finds out. + the second tenant finds out. All three commands now answer identically by + construction — `os lint` and `os compile` were letting through react pages + and readonly flow writes that `os validate` rejected, including the field + resolution that **gates** (an unresolvable `` predicate + yields a silently empty list, indistinguishable from no data). Expect CI + running `os lint` to newly fail on metadata that was already broken. - **Service slots are typed contracts (#4251, #4127).** `IObjectQLEngine` lands (the seven consumer-local stand-in surfaces die), `getService` resolves through a slot→contract ledger, and a shrinking baseline bans From 8c70398a44da89d7b9f444f2e29937e5a1ae73c8 Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Jul 2026 13:25:44 +0000 Subject: [PATCH 4/5] docs(releases): declare the changeset, and backfill the 27 objectui fixes the bump missed MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two things the `Check Changeset` gate and a verification pass turned up. **This PR declares itself.** An empty-frontmatter changeset — the sanctioned "releases nothing" declaration the gate documents — for the docs-only v17 page work. **The Console window was under-enumerated, and the page would have inherited that.** Cross-checking the five `console-*` changesets against objectui's own history found `console-bebaebd39ace.md` covers only the tail of its range: 94 first-parent commits, 40 enumerated, oldest entry #3026. The other four ranges reconcile exactly. Everything that merged earlier inside that one range is in neither the release history nor the curated page — the same failure `console-c6cfdf1288b6-backfill.md` records, now for the second time. Adds the backfill (patch; all 27 are `fix`, none `feat`, so the bump script's own typing rule applies) declaring no SHA move. Several are data-loss fixes an upgrading Console user feels on contact: a tabbed modal or split form silently dropping the values of every pane but the active one, a `defaultValues` change discarding what the user was typing, no unload guard on a dirty modal, and a flow or action that failed under HTTP 200 still reporting success. Also fixes the `check:role-word` failure this branch introduced — the ADR-0090 D3 vocabulary is permission sets and positions, so the upgrade-checklist entry now says which permission sets carry `manage_sharing` rather than "roles". Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MevuZ96T3E9yivS8QvghcR --- .changeset/console-bebaebd39ace-backfill.md | 56 +++++++++++++++++++++ .changeset/v17-page-rc1-window.md | 31 ++++++++++++ content/docs/releases/v17.mdx | 7 +-- 3 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 .changeset/console-bebaebd39ace-backfill.md create mode 100644 .changeset/v17-page-rc1-window.md diff --git a/.changeset/console-bebaebd39ace-backfill.md b/.changeset/console-bebaebd39ace-backfill.md new file mode 100644 index 0000000000..170d148fd7 --- /dev/null +++ b/.changeset/console-bebaebd39ace-backfill.md @@ -0,0 +1,56 @@ +--- +"@objectstack/console": patch +--- + +Console (objectui) backfill for `96ee72e85439...bebaebd39ace` — the 27 fix +commits that refresh's changeset did not enumerate. + +`scripts/bump-objectui.sh` emits a `@objectstack/console` changeset on every +bump precisely so a SHA move leaves a trace (see `docs/releases-maintenance.md`), +and the `console-bebaebd39ace.md` entry it wrote covers only the tail of its own +range: the range holds **94** first-parent commits, the enumeration lists 40, +and its oldest entry is #3026. Everything that merged earlier inside the same +range went unrecorded — in the release history and in the curated v17 page. +This is the second instance of the failure `console-c6cfdf1288b6-backfill.md` +records; it declares no SHA move (`.objectui-sha` already points at +`7d9734d5e321`, past this range). + +The 27 are all `fix`, hence `patch`. Several are data-loss fixes an upgrading +Console user feels immediately: + +- fix(form): a tabbed/sectioned modal keeps every tab's values (#2959, #2153) (#2987) +- fix(form): a split form keeps BOTH panels' values (#2153) (#3012) +- fix(form): a defaultValues change no longer discards the field being filled (#2982) (#2991) +- fix(components): apply new form defaultValues in the commit that renders them (#3001) +- fix(plugin-form): block page unload while a modal/drawer form has unsaved input (#2998) +- fix(plugin-form): swapping recordId no longer leaves the previous record on screen (#3005) +- fix(plugin-form): a wizard that ends on a field-less review step can finish (#2986) +- fix(form): a tabbed/split form honours the form view's own `columns` (#3018) +- fix(console): a flow or action that failed under HTTP 200 stops reporting success (#2958) (#2995) +- fix(grid): a legacy string row action runs instead of green-toasting a no-op (#2960) (#2996) +- fix(spec-parity): render the six Tier-1 spec values right instead of silently wrong (#2941) (#2993) +- fix(spec-parity): the Tier-2 spec values render instead of validating into nothing (#2942) (#3008) +- fix(spec-parity): the Tier-3 spec values render instead of red-boxing (#2943) (#3011) +- fix(view,components): the spec→FilterBuilder operator table covers the whole view vocabulary (#2945) (#2989) +- fix(view): the spec→FilterBuilder map follows the four operators #2942 added (#3022) +- fix(charts): a spec `series[].type` draws, and a spec-shape `series` plots at all (#2945) (#3004) +- fix(charts): say so when rows carry no category key, instead of drawing an empty axis (#3007) +- fix(analytics): a missing analytics capability no longer renders as an empty KPI (objectstack#3891) (#2981) +- fix(chatbot): read the agent catalog in the declared envelope too (objectstack#4053) (#2992) +- fix(sdui): a react page keeps its state; a source that exports nothing fails loudly (#2984) +- fix(sdui): a kind:'html' page can use lazily-registered blocks, and recovers when one registers late (#2988) +- fix(sdui): stop the react page's "no adapter yet" fallback churning its provider context (#3000) +- fix(sdui): the curated contract lists record:line_items, the tag that actually resolves (#3006) +- fix(record): register the record:* blocks under one key, prefixed once (#3023) +- fix(plugin-list,plugin-grid): drop undeliverable formats from the export menu (#2999) +- fix(components): a stacked resizable group gets a divider, not a 1px sliver (#3024) +- fix(components,app-shell): the last two `direction` props follow v4's rename to `orientation` (#3025) + +Also in the range and deliberately not listed here: the refactor/chore/test/ +build/ci PRs the bump script's fix/feat filter excludes by design — including +three breaking-flagged refactors already reflected in the spec-side work +(#2990 the `execute` alias deleted from the action runner, objectstack#3856; +#3003 action sub-vocabularies derived from spec, and #3020 authoring types +become input types, both objectstack#4074). + +objectui range: `96ee72e85439...bebaebd39ace` diff --git a/.changeset/v17-page-rc1-window.md b/.changeset/v17-page-rc1-window.md new file mode 100644 index 0000000000..81fd486b1f --- /dev/null +++ b/.changeset/v17-page-rc1-window.md @@ -0,0 +1,31 @@ +--- +--- + +Docs-only: carry the v17 release page across the `rc.0 → rc.1` window. Releases +nothing. + +The page stopped at the rc.0 cut while 334 changesets (40 `major`-class) and +five objectui pin moves landed on `main` — the layer-3 curated page +`docs/releases-maintenance.md` makes mandatory for a major, left a release +behind the train it documents. + +Adds a **Landed since 17.0.0-rc.0** section organised by who has to act on it: +the metadata and protocol changes an upgrading application developer must read +(the `/actions` HTTP contract, list queries that apply-or-fail, the one alias +fold across every engine method, `query.having` becoming real, flow-node config +enforcement, stored-metadata conversion replay, the temporal campaign, +ADR-0111 sharing authority), then the platform capabilities an administrator +gains (honest 501s naming the package to install, `os migrate` writing nothing +before confirmation, boot-log visibility, platform-objects infrastructure, +cron jobs that finally fire), then a security-corrections subsection for the +four fail-opens closed in this window, then the Console delta. + +The largest post-rc.0 landings (ADR-0110, ADR-0104 D2, ADR-0113, ADR-0114, the +#4001 strictness clicks, the seven protocol-17 retirements, the #4212 family, +the #3896 sweep) were already written into the sections above as they landed; +the new section names them and points there rather than restating them, so the +page cannot grow two accounts of one change that drift apart. + +Also extends the upgrade checklist with this window's consumer-facing actions +and the References list with its ADRs and PRs, and scopes the existing Console +section to the rc.0 window it actually documents. diff --git a/content/docs/releases/v17.mdx b/content/docs/releases/v17.mdx index 8f88eada72..f8310c175c 100644 --- a/content/docs/releases/v17.mdx +++ b/content/docs/releases/v17.mdx @@ -2057,9 +2057,10 @@ rc.1, and administrators should know what changed: - **Dev setups:** the fabricating stubs are gone — install the real plugin for any slot a dev call now finds absent, and note `plugin-dev` refuses `NODE_ENV=production` without `OS_ALLOW_DEV_PLUGIN=1` (ADR-0115). -- **Sharing admins:** grant the new `manage_sharing` capability to roles that - administer sharing rules, and expect edit-level shares to stop conferring - delete (ADR-0111). +- **Sharing admins:** add the new `manage_sharing` capability to the permission + sets held by whoever administers sharing rules (`admin_full_access` carries + it already), and expect edit-level shares to stop conferring delete + (ADR-0111). - **Flow authors:** rename aliased node-config keys (`subflow`/`map` `flow` → `flowName`, notify `to`/`subject`/`body`/`url` → `recipients`/`title`/`message`/`actionUrl`, script `functionName`/`input` → From 1192780bdf3b079d8f9d3949c088e42c26a243ce Mon Sep 17 00:00:00 2001 From: Claude Date: Fri, 31 Jul 2026 13:26:23 +0000 Subject: [PATCH 5/5] docs(releases): fold the backfilled Console fixes into the v17 page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The backfill changeset records the 27 unenumerated objectui fixes in `@objectstack/console`'s changelog (the detail layer). The curated page is the layer that has to tell an upgrading user what they will feel, so the user-visible half lands here too rather than being left to a changelog nobody reads before upgrading. Two additions: a "forms stop losing what you typed" group — the tabbed-modal and split-form value loss, `defaultValues` discarding in-progress input, the missing unload guard, the stale record after a `recordId` swap — and a "spec values render instead of failing three different ways" group covering the three spec-parity tiers, the FilterBuilder operator table, the chart series/category fixes, the empty-KPI analytics case, legacy string row actions, and the HTTP-200 failure reporting as success. The section header now states the real window size (143 commits) and names the backfill, so the 79-PR enumeration is not mistaken for the whole of it. Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01MevuZ96T3E9yivS8QvghcR --- content/docs/releases/v17.mdx | 23 +++++++++++++++++++++-- 1 file changed, 21 insertions(+), 2 deletions(-) diff --git a/content/docs/releases/v17.mdx b/content/docs/releases/v17.mdx index f8310c175c..3c3ac3da59 100644 --- a/content/docs/releases/v17.mdx +++ b/content/docs/releases/v17.mdx @@ -1854,7 +1854,10 @@ rc.1, and administrators should know what changed: ### New in Console — bundled objectui advanced `4a4829d0ef39 → 7d9734d5e321` -79 objectui commits across five pin moves, released as **objectui 17.1.0**. +143 objectui commits across five pin moves, released as **objectui 17.1.0**. +(The pin changesets enumerate 79 of them; one range under-enumerated its own +window and is recorded by `console-bebaebd39ace-backfill` — the fixes it +covers are folded into the list below rather than left to the changelog.) - **The lockstep half of ADR-0110 — release-critical.** The rc.0 pin predates the client fix, so the Console it built still posted `action.target` to @@ -1882,13 +1885,29 @@ rc.1, and administrators should know what changed: downloads the searched rows, not the unsearched superset; and every column resolver reads one identity key, resolved at ingestion and reported out loud. +- **Forms stop losing what you typed.** A tabbed or sectioned modal keeps + **every** tab's values and a split form keeps **both** panels' — previously + only the active pane survived the save; a `defaultValues` change no longer + discards the field being filled (and new defaults apply in the commit that + renders them); leaving a page with unsaved input in a modal or drawer form + is guarded; swapping `recordId` no longer leaves the previous record on + screen; and a wizard that ends on a field-less review step can finish. - **Edits are safer.** Form edit saves send `If-Match` and surface 409 conflicts instead of silently overwriting; a wizard with `allowSkip` no longer submits past skipped fields; a select no longer wipes itself when its value outruns its options; numeric and boolean option values survive selection typed; multi-value lookup is selectable in inline edit and hydrates through a batched `$in` (showing loading rather than the empty - placeholder). + placeholder); tabbed and split forms honour the form view's own `columns`. +- **Spec values render instead of failing three different ways.** Three tiers + of spec-declared values were silently wrong, validated into nothing, or + red-boxed the surface; they now render. The spec→FilterBuilder operator + table covers the whole view vocabulary, a spec `series[].type` draws (and a + spec-shape `series` plots at all), a chart says so when its rows carry no + category key instead of drawing an empty axis, a missing analytics + capability stops rendering as an empty KPI, a legacy string row action runs + instead of green-toasting a no-op, and a flow or action that failed under + HTTP 200 stops reporting success. - **Bulk actions become per-record.** An object-declared bulk action runs over the selection, `visible` is evaluated per selected record, params render through the shared form-field widgets (lookup errors get Retry, `sys_user`