From a605ef383eb2bb42a4ed6c227d2e38d823ead888 Mon Sep 17 00:00:00 2001 From: Jack Zhuang <277994282+os-zhuang@users.noreply.github.com> Date: Sun, 14 Jun 2026 07:15:42 +0500 Subject: [PATCH] chore(changeset): backfill changesets for post-9.3 changes Adds changesets for the package-touching PRs merged after the 9.3.0 release that landed without one: objectql (metadata hydration opt-in, seed id fallback, ADR-0048 namespace gate + package-scoped resolution), service-ai (visualize_data tool + steering, query-only open framework, cross-language current-object), driver-sql ($select zero-rows fix), and spec (ADR-0047 list-page form + filter-mode widget). Co-Authored-By: Claude Opus 4.8 (1M context) --- ...-sql-unknown-select-column-no-zero-rows.md | 18 +++++++++++++++ ...ql-adr-0048-namespace-gate-prefer-local.md | 23 +++++++++++++++++++ ...bjectql-hydrate-metadata-from-db-opt-in.md | 21 +++++++++++++++++ .../objectql-seed-reference-id-fallback.md | 17 ++++++++++++++ ...ervice-ai-current-object-cross-language.md | 22 ++++++++++++++++++ .../service-ai-open-framework-query-only.md | 19 +++++++++++++++ .changeset/service-ai-steer-visualize-data.md | 20 ++++++++++++++++ .changeset/service-ai-visualize-data-tool.md | 21 +++++++++++++++++ .../spec-adr-0047-filter-mode-widget.md | 16 +++++++++++++ .changeset/spec-adr-0047-list-page-form.md | 15 ++++++++++++ 10 files changed, 192 insertions(+) create mode 100644 .changeset/driver-sql-unknown-select-column-no-zero-rows.md create mode 100644 .changeset/objectql-adr-0048-namespace-gate-prefer-local.md create mode 100644 .changeset/objectql-hydrate-metadata-from-db-opt-in.md create mode 100644 .changeset/objectql-seed-reference-id-fallback.md create mode 100644 .changeset/service-ai-current-object-cross-language.md create mode 100644 .changeset/service-ai-open-framework-query-only.md create mode 100644 .changeset/service-ai-steer-visualize-data.md create mode 100644 .changeset/service-ai-visualize-data-tool.md create mode 100644 .changeset/spec-adr-0047-filter-mode-widget.md create mode 100644 .changeset/spec-adr-0047-list-page-form.md diff --git a/.changeset/driver-sql-unknown-select-column-no-zero-rows.md b/.changeset/driver-sql-unknown-select-column-no-zero-rows.md new file mode 100644 index 0000000000..40d857e5af --- /dev/null +++ b/.changeset/driver-sql-unknown-select-column-no-zero-rows.md @@ -0,0 +1,18 @@ +--- +"@objectstack/driver-sql": patch +--- + +fix(driver-sql): an unknown `$select` column must not zero the result set + +`find()` swallowed any "no such column" error into an empty array. A projected +`$select` naming a column the table lacks (e.g. a generic list view +auto-requesting `status`/`due_date`/`image` on an object without them) then made +the WHOLE query return zero rows — reading to the UI as "no records exist" while +the data was actually there: a silent data-loss footgun. + +When the failure comes from the projection, retry once with `SELECT *` so the +real rows still come back (the phantom field is simply absent from each row). +Non-projection errors (unknown table, etc.) still surface as before. This driver +backstop holds even when the engine's unknown-field filter cannot fire because +the object's schema is not populated in the registry (notably the cloud +multi-tenant runtime). diff --git a/.changeset/objectql-adr-0048-namespace-gate-prefer-local.md b/.changeset/objectql-adr-0048-namespace-gate-prefer-local.md new file mode 100644 index 0000000000..5e76fe506a --- /dev/null +++ b/.changeset/objectql-adr-0048-namespace-gate-prefer-local.md @@ -0,0 +1,23 @@ +--- +"@objectstack/objectql": minor +--- + +feat(objectql): ADR-0048 Phase 1+2 — namespace install gate + package-scoped resolution + +Phase 1 — install-time namespace gate. `SchemaRegistry.installPackage` refuses a +package whose `manifest.namespace` is already owned by a DIFFERENT installed +package (new `NamespaceConflictError`), making explicit and early the constraint +the table layer already enforces implicitly. Same-package reinstall and +shareable platform namespaces (`base`/`system`/`sys`) are exempt; +`OS_METADATA_COLLISION=warn` downgrades to a warning. + +Phase 2 — prefer-local resolution, pivoted to ADR-0048 option A (package id as +the routing key). `getItem(type, name, currentPackageId?)` prefers +`${currentPackageId}:${name}` before any cross-package fallback (ADR-0005 overlay +precedence and backward compatibility unchanged); `getApp(name, +currentPackageId?)` resolves prefer-local by package id. Because package ids are +globally unique, package-scoped resolution always disambiguates two distinct +packages — so the old per-item CROSS-package throw (and the now-dead +`MetadataCollisionError`, `findOtherPackageOwner`, `SYS_METADATA_OWNER`, …) is +retired; two different-namespace packages legitimately coexist on the same bare +name. `collisionPolicy` now governs only the Phase 1 namespace gate. diff --git a/.changeset/objectql-hydrate-metadata-from-db-opt-in.md b/.changeset/objectql-hydrate-metadata-from-db-opt-in.md new file mode 100644 index 0000000000..810c89716f --- /dev/null +++ b/.changeset/objectql-hydrate-metadata-from-db-opt-in.md @@ -0,0 +1,21 @@ +--- +"@objectstack/objectql": minor +--- + +feat(objectql): opt-in `sys_metadata` hydration for isolated project kernels + +Boot Phase-2 hydration (`restoreMetadataFromDb` → `loadMetaFromDb`, which +registers objects WITH their fields into the `SchemaRegistry`) was gated on +`environmentId === undefined`, assuming every project kernel sources its +metadata from a remote artifact / control-plane proxy. That is untrue for an +isolated, proxy-free project kernel that persists its OWN `sys_metadata` +locally (the cloud single-env tenant runtime): objects created at runtime there +never re-entered the registry after a restart, so `registry.getObject(name)` +returned nothing and every registry consumer silently degraded (notably the +`engine.find` unknown-`$select` guard, which then let an unknown projected +column zero the result set). + +Adds an explicit `hydrateMetadataFromDb` plugin option (default `false`, so the +control-plane/proxy path is untouched). When set, hydration runs even with +`environmentId` defined — safe because each engine now owns its registry +instance and `loadMetaFromDb` already tolerates a missing table. diff --git a/.changeset/objectql-seed-reference-id-fallback.md b/.changeset/objectql-seed-reference-id-fallback.md new file mode 100644 index 0000000000..b2a651dc14 --- /dev/null +++ b/.changeset/objectql-seed-reference-id-fallback.md @@ -0,0 +1,17 @@ +--- +"@objectstack/objectql": patch +--- + +fix(objectql): seed reference resolution falls back to matching by `id` + +`SeedLoaderService.resolveFromDatabase` only matched a reference value against +the target's natural-key field. A seed that wires a lookup to a REAL existing +record by its internal id — e.g. a people field (approver/applicant → user) +pointed at the current user — dangled to `null` when that id is not a +UUID/ObjectId (so the caller's `looksLikeInternalId` guard did not +short-circuit) and is not the target's natural key. + +Adds an id fallback: when the natural-key lookup finds nothing, try resolving +the value as the target's `id`. Safe — an id either exists or it doesn't, so +there is no risk of a false natural-key match, and it is tenant-scoped like the +primary lookup. diff --git a/.changeset/service-ai-current-object-cross-language.md b/.changeset/service-ai-current-object-cross-language.md new file mode 100644 index 0000000000..749f1e6f67 --- /dev/null +++ b/.changeset/service-ai-current-object-cross-language.md @@ -0,0 +1,22 @@ +--- +"@objectstack/service-ai": patch +"@objectstack/spec": patch +--- + +fix(service-ai): resolve the current object for AI chat across languages + +The console assistant reported "can't find the X object" when asked to analyse +the object on the current page — most visibly for non-English prompts. Three +compounding gaps fixed: + +- `SchemaRetriever.tokenise()` dropped all CJK text, so a Chinese request + yielded zero terms; it now emits CJK single-char + bigram terms. +- Nothing fed the current object's schema to the agent, so "this object" could + not be resolved without a lucky keyword hit. `AgentRuntime.buildContextSchema + Messages()` now injects the current object's schema into the system prompt and + both chat routes call it. +- `ToolExecutionContext` (and the `ai-service` spec contract) gains + `currentObjectName`/`currentViewName`; routes thread them through and + `query_data` falls back to the current object when keyword retrieval is empty + (so the open edition, which lacks `describe_object`/`list_objects`, still + resolves the page's object). diff --git a/.changeset/service-ai-open-framework-query-only.md b/.changeset/service-ai-open-framework-query-only.md new file mode 100644 index 0000000000..513ea14d66 --- /dev/null +++ b/.changeset/service-ai-open-framework-query-only.md @@ -0,0 +1,19 @@ +--- +"@objectstack/service-ai": minor +--- + +feat(service-ai): open framework AI is query-only, declines app-building + +The unified `data_chat` persona (ADR-0040) advertised that it can BUILD or +CHANGE the application, but that capability is supplied entirely by the cloud AI +Studio plugin's `metadata_authoring`/`solution_design` skills. On the open +single-env framework those skills are not registered, so the authoring tools +never resolve — yet the LLM, still reading the "you can build" persona, +role-played designing a whole system (emitting design docs it had no tools to +execute). + +Fix: in `buildSystemMessages`, when no authoring (build-register) skill is +active, append a deployment-capability note constraining the assistant to +data/query and instructing it to decline build requests instead of pretending. +Keyed off actual skill presence, so cloud/EE (AI Studio loaded) keeps the full +build UX with zero extra wiring. diff --git a/.changeset/service-ai-steer-visualize-data.md b/.changeset/service-ai-steer-visualize-data.md new file mode 100644 index 0000000000..09e9bb8d8c --- /dev/null +++ b/.changeset/service-ai-steer-visualize-data.md @@ -0,0 +1,20 @@ +--- +"@objectstack/service-ai": minor +--- + +feat(service-ai): steer the agent to `visualize_data` for chart requests + +Small models sometimes answered a "draw a bar chart" request with a markdown +TABLE instead of calling `visualize_data` — a tool-selection problem where the +chart preference was buried as a low-priority guideline competing with +"format with markdown tables". + +- `data-explorer-skill.ts` — adds a prominent "Choosing the right tool" section + above the guidelines: chart intent (incl. CN terms 图表/柱状图/折线图/饼图/画图) + MUST call `visualize_data`, never substitute a table; reconciles the + table-formatting guideline and fixes duplicate guideline numbering. +- `visualize-data.tool.ts` — strengthens the tool description to be imperative + ("the ONLY tool that draws a chart… if you already fetched the numbers, still + call `visualize_data` to render them"). + +Prompt-only tuning — no behavior/contract change. diff --git a/.changeset/service-ai-visualize-data-tool.md b/.changeset/service-ai-visualize-data-tool.md new file mode 100644 index 0000000000..18818026e7 --- /dev/null +++ b/.changeset/service-ai-visualize-data-tool.md @@ -0,0 +1,21 @@ +--- +"@objectstack/service-ai": minor +--- + +feat(service-ai): `visualize_data` tool — return charts from AI data queries + +Adds a `visualize_data` AI tool so the data-query assistant can answer with a +CHART instead of plain text/markdown. The tool runs an aggregation through the +existing analytics service (auto-inferred cube), maps the result into the SDUI +`` contract, and emits it to the client as a `data-chart` custom stream +part (the same `onProgress` channel `data-build-progress` already uses). It also +returns a compact textual summary so the model narrates the answer alongside the +rendered chart. + +- `tools/visualize-data.tool.ts` — tool definition, handler, and register fn + (function+field → analytics measure key; single dimension → x-axis; measures → + series; `chartType` bar/line/pie/…). +- `plugin.ts` — registers the tool when an analytics service is present and + persists it as tool metadata in lockstep (Studio visibility). +- `skills/data-explorer-skill.ts` — exposes `visualize_data` plus chart trigger + phrases and guidance to prefer it for "chart/plot/trend/breakdown" requests. diff --git a/.changeset/spec-adr-0047-filter-mode-widget.md b/.changeset/spec-adr-0047-filter-mode-widget.md new file mode 100644 index 0000000000..d77ffe960c --- /dev/null +++ b/.changeset/spec-adr-0047-filter-mode-widget.md @@ -0,0 +1,16 @@ +--- +"@objectstack/spec": minor +--- + +feat(spec): page form filter-mode widget + ADR-0047 §3.4a (omit-is-none) + +The Interface section's `interfaceConfig` composite now lists its sub-fields +explicitly so `userFilters` can use the dedicated `filter-mode` selector widget +(None / Tabs / Dropdown, objectui). An unknown widget name degrades gracefully +to the prior composite rendering, so this is independently mergeable. + +ADR-0047 §3.4a records the design decision: "no filter bar" is the ABSENCE of +`userFilters`, not a literal `element: 'none'` — presence and style are +orthogonal axes, keeping declarative metadata and overlay diffs clean. The +`userFilters` element `'toggle'` is deprecated (kept in the enum for back-compat; +authoring offers None/Tabs/Dropdown only, Airtable parity). diff --git a/.changeset/spec-adr-0047-list-page-form.md b/.changeset/spec-adr-0047-list-page-form.md new file mode 100644 index 0000000000..613d3b8840 --- /dev/null +++ b/.changeset/spec-adr-0047-list-page-form.md @@ -0,0 +1,15 @@ +--- +"@objectstack/spec": minor +--- + +feat(spec): ADR-0047 — list pages hide region/data-context, interface section prominent + +Reorganizes the page form (`page.form.ts`) so interface/list pages get a lean, +relevant panel instead of the generic page-form dump: + +- Data Context + Layout sections gain `visibleOn` `data.type != 'list'` (region + designer / page object don't apply to a list surface). +- Interface section becomes primary content (`collapsed: false`, named for i18n). +- `interfaceConfig` sub-fields reordered (common first, rare last); `source` + gets the `ref:object` picker; `sourceView`/`userActions`/etc. gain helpText. +- `type` field helpText notes `'list'` = interface page.