Skip to content

Commit 4f73edb

Browse files
committed
Merge origin/main into claude/formviewschema-button-defaults-77q7fs
Conflict resolution: content/docs/references/ui/view.mdx (auto-generated) regenerated via gen:docs on the merged tree; api-surface.json auto-merge verified identical to regenerated output; json-schema.manifest.json (disappearance ratchet, #3012, new on main) picks up +ui/FormButtonConfig. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Ue47V8zZ5QhcYMPiRQDA7
2 parents 596caeb + a16972b commit 4f73edb

193 files changed

Lines changed: 11613 additions & 894 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/runtime': minor
4+
'@objectstack/metadata-protocol': minor
5+
'@objectstack/objectql': patch
6+
'@objectstack/rest': patch
7+
'@objectstack/plugin-hono-server': patch
8+
---
9+
10+
feat(discovery): honest capabilities — standardized stub/fallback marker + realtime route honesty (ADR-0076 D12/A1.5 framework slice, #2462)
11+
12+
**Spec** — new service self-description marker for honest discovery
13+
(ADR-0076 D12): `SERVICE_SELF_INFO_KEY` (`__serviceInfo`),
14+
`ServiceSelfInfoSchema` / `ServiceSelfInfo`, and `readServiceSelfInfo()`,
15+
which also normalizes plugin-dev's legacy `_dev: true` flag to
16+
`{ status: 'stub', handlerReady: false }`. A registered service that is a
17+
stub / dev fake / degraded fallback self-identifies via this marker; a fully
18+
real service carries no marker.
19+
20+
**Runtime + metadata-protocol** — both discovery builders
21+
(`HttpDispatcher.getDiscoveryInfo` and the protocol shim's `getDiscovery`)
22+
now honor the marker instead of hardcoding `status: 'available',
23+
handlerReady: true` for every registered service. Dev stubs report `stub`,
24+
the ObjectQL analytics fallback reports `degraded` (it keeps serving — no
25+
`/analytics` 404), and consumers can finally trust
26+
`status === 'available'` / `handlerReady === true`.
27+
28+
**Realtime honesty fix** — discovery no longer advertises a
29+
`/realtime` route or `websockets: true`: `service-realtime` is an
30+
in-process pub/sub bus, no dispatcher branch or plugin mounts any
31+
`/realtime` HTTP surface, so the advertised route always 404'd. The
32+
registered service now reports `status: 'degraded', handlerReady: false`
33+
with no route (clients using the SDK are unaffected — it falls back to the
34+
conventional path, which behaves exactly as before). Also corrects the
35+
advertised realtime provider from the nonexistent `plugin-realtime` to
36+
`service-realtime`.
37+
38+
**REST (A1.5)** — the REST layer's protocol dependency is narrowed from the
39+
`ObjectStackProtocol` god-union to the new `RestProtocol =
40+
DataProtocol & MetadataProtocol` slice (exported from
41+
`@objectstack/rest`), per the ADR-0076 D9 incremental narrowing guidance.
42+
Type-level only; no runtime change.
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/lint': patch
3+
'@objectstack/plugin-audit': patch
4+
---
5+
6+
ADR-0085 #2548 follow-ups surfaced by the real-backend browser pass:
7+
8+
- **lint**: new `field-group-shadowed` warning in `validate-semantic-roles` — a
9+
declared fieldGroup whose every visible member is hoisted into the detail
10+
highlight strip (or is the record title) renders on forms but silently never
11+
on detail pages (detail bodies hide the first 4 highlightFields). Warning
12+
tier, same as the other semantic-role rules.
13+
- **plugin-audit**: feed/audit summaries ("Created … / Deleted … / Updated …")
14+
now name the object by its display label ("Semantic Zoo") instead of its API
15+
name ("showcase_semantic_zoo") — these strings render verbatim in the record
16+
Discussion feed and Setup dashboards. Falls back to the API name when the
17+
object definition isn't resolvable. Existing stored rows are unchanged.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): retire the stale `renderViaSchema` forward-reference now that objectui#2546 landed (ADR-0085 PR4 follow-up, #2548)
6+
7+
The `ObjectSchema` source comment forward-referenced `renderViaSchema`
8+
retiring "together with the legacy monolith render path" — a promise about
9+
work that had not yet shipped. That path, and the `detail.renderViaSchema`
10+
kill-switch that was its only steering wheel, were removed in objectui#2546
11+
(ADR-0085 PR4). The comment now records the completed state with a breadcrumb
12+
to that PR instead of a forward reference, closing the cleanup #2546 flagged.
13+
14+
Comment-only change; no type, schema, or runtime behavior is affected.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/service-automation': minor
4+
'@objectstack/connector-openapi': minor
5+
'@objectstack/cli': minor
6+
---
7+
8+
feat(connector-openapi): resolve `providerConfig.spec` from a package-relative file path (#3016, ADR-0096 follow-up)
9+
10+
ADR-0096's canonical example authors an OpenAPI-backed instance as
11+
`providerConfig: { spec: './billing-openapi.json' }`, but the landed `openapi`
12+
provider factory only accepted an inline document object or an http(s) URL.
13+
The spec union is now complete: **inline object | file path | remote URL**.
14+
15+
- **`@objectstack/spec`.** `ConnectorProviderContext` gains an optional
16+
host-injected `loadPackageFile(relativePath)` capability (pure type): reads a
17+
UTF-8 file resolved against the declaring stack/package root, confined to
18+
that root. `undefined` on hosts without a filesystem.
19+
20+
- **`@objectstack/service-automation`.** New `packageRoot` plugin option (the
21+
base for relative file refs; defaults to `process.cwd()`) and an exported
22+
`createPackageFileLoader(packageRoot)` that implements the confinement
23+
guard — absolute paths and `..`-escaping paths are rejected — with lazy
24+
`node:fs`/`node:path` imports so non-Node hosts only fail if a file ref is
25+
actually dereferenced. The materializer injects the capability into every
26+
provider factory's context. Failures follow the existing reconcile policy:
27+
**fatal at boot, entry skipped on reload**.
28+
29+
- **`@objectstack/connector-openapi`.** A string `providerConfig.spec` that is
30+
not an http(s) URL is now read via `ctx.loadPackageFile` and parsed as an
31+
OpenAPI JSON document (clear errors for missing/unreadable files, unparseable
32+
JSON, and hosts without package file access).
33+
34+
- **`@objectstack/cli`.** `serve`/`dev` pass the project folder (the
35+
`objectstack.config.ts` directory) as the automation service's `packageRoot`,
36+
mirroring how the standalone sqlite default is anchored.
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
---
2+
'@objectstack/service-automation': minor
3+
---
4+
5+
feat(connectors): ADR-0096 runtime re-materialization of declarative connectors (#2977 follow-up)
6+
7+
Provider-bound declarative `connectors:` instances (ADR-0096) previously
8+
materialized only at boot — a connector published from Studio while the server
9+
ran did not become dispatchable until a restart. `materializeDeclaredConnectors`
10+
is now a **reconcile** run both at boot and on `metadata:reloaded`:
11+
12+
- **Add** newly-declared instances, **tear down** removed / newly-`enabled:false`
13+
ones (calling their `close`, e.g. an MCP connection), and **re-materialize**
14+
only instances whose signature — a stable hash of `provider` + `providerConfig`
15+
+ `auth` + identity — changed. An unchanged MCP instance is never needlessly
16+
reconnected on an unrelated metadata reload.
17+
- **Boot stays fatal** ("fail loudly"): unknown provider / invalid providerConfig
18+
/ unresolvable credentialRef / name conflict aborts startup. **Reload is soft**:
19+
the same problems are logged and the offending entry skipped, so a bad publish
20+
never crashes a running server; a changed instance's old connector keeps
21+
serving until its replacement materializes successfully.
22+
23+
Also: `ConnectorDescriptor` (served by `GET /api/v1/automation/connectors`) now
24+
carries an `origin` field (`'plugin' | 'declarative'`), so a designer can
25+
distinguish a materialized declarative instance from a plugin-registered
26+
connector.

.changeset/config.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
"@objectstack/plugin-email",
4242
"@objectstack/plugin-hono-server",
4343
"@objectstack/mcp",
44+
"@objectstack/plugin-pinyin-search",
4445
"@objectstack/plugin-reports",
4546
"@objectstack/plugin-security",
4647
"@objectstack/plugin-sharing",

.changeset/console-092bd859934f.md

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
---
2+
"@objectstack/console": patch
3+
---
4+
5+
Console (objectui) refreshed to `092bd859934f`. Frontend changes in this range:
6+
7+
- fix(app-shell): bind current_user.positions into the client predicate scope; align role-gating examples (#1583 / ADR-0058) (#2573)
8+
- feat(app-shell): CEL lint + field autocomplete for condition predicates (#1582) (#2567)
9+
- fix(detail): gate related lists on the current user's child-object read permission (#2359) (#2565)
10+
- feat(flow-designer): connector picker lists dispatchable connectors + marks declarative instances (#2563)
11+
- feat(app-shell): Studio CEL editor for list-view conditional formatting (#1584 / #1582) (#2558)
12+
- fix(fields): resolve lookup chip display name via referenced object schema, not the autonumber-prone key heuristic (#2357) (#2551)
13+
- feat(kanban): accept CEL { condition, style } conditional-formatting rules (#1584 follow-up) (#2550)
14+
- fix(plugin-grid): sniff CSV encoding in import wizard (GB18030 fallback) (#2557)
15+
- feat(detail): editable record highlights on the shared inline-edit draft (#2549)
16+
- fix(spec-bridge/form): #2545 stop dropping spec FormViewSchema keys; normalize legacy groups → sections (#2552)
17+
- feat(flow-designer): localize palette headings + cloud-sync recents + guide (#2553)
18+
- fix(studio): refresh builder top-bar name after a package rename (#2554)
19+
- feat(core): B3 cascading-option guardrail, role-gated demo, ADR + browser e2e (#1583) (#2547)
20+
- feat(list): unify conditional formatting + row-action visibility onto the CEL engine (#1584) (#2544)
21+
- feat(detail): record-level inline edit — shared InlineEditContext + one atomic Save (#2542)
22+
- feat(flow-designer): search box + keyboard nav + recents in the add-node palette (#2543)
23+
- feat(kanban): default card fields to object highlightFields (ADR-0085, #2162) (#2541)
24+
- fix(types/plugin-grid): #1763 declare spec-canonical bulkActions on ObjectGridSchema (#2539)
25+
- fix(attachments): download attachments via authenticated signed URL (framework #2970)
26+
- feat(studio): spec-driven package create/edit/view form in a modal (#2535)
27+
- fix(permissions/fields): #2926 ④⑧ — FLS fail-open + lookup display_field (#2537)
28+
- feat(app-shell): CEL authoring safety for RLS policies — lint, field autocomplete, test-run (#2533)
29+
- fix(auth): gate DeviceAuthPage on features.deviceAuthorization (framework#2874 / #2513) (#2536)
30+
- fix(app-shell): close view config panel on discard in edit mode (#2320)
31+
- feat(metadata-admin): create form-family views through the View create UI (#2531)
32+
- fix(app-shell): render action's objectName as an object selector (#2325)
33+
- fix(components): exit inline edit mode for injected cell editors (#2534)
34+
- fix(attachments): authenticated uploads + friendly denial copy in RecordAttachmentsPanel (#2755) (#2532)
35+
- feat(components): page:tabs honors item-level visibleWhen — conditional tabs (framework#2606) (#2516)
36+
- feat(metadata-admin): page variable source is a component picker (#2328) (#2523)
37+
- fix(studio-design): make object canvas overridable via studio-canvas-preview registry (#2337) (#2527)
38+
- fix(metadata-admin): seed flow createDefaults with required `type` (#2525)
39+
- fix(metadata-admin): give hook create form a createSchema so object renders as a ref:object picker (#2521)
40+
- feat(studio): enforce package namespace prefix at authoring time (framework#2694) (#2524)
41+
- fix(app-shell): render View create-form Object field as ref:object picker (#2526)
42+
- feat(components): add `variant === 'primary'` tie-break to action:bar ordering (#2339) (#2519)
43+
- fix(build): stop TS6059 rootDir errors in dts build across 21 packages (#2520)
44+
- fix(app-nav): exclude record-detail pages from the 'page' nav picker (#2333) (#2517)
45+
- fix(plugin-report): stop TS6059 rootDir errors in dts build (#2334) (#2518)
46+
- feat(flow-designer): add 'position' xRef picker kind for approval approvers / escalateTo (#2778) (#2515)
47+
48+
objectui range: `cc2156841787...092bd859934f`
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
Dashboard-level filters spec pairing (framework#2501, objectui#2578) — land the
6+
two properties the objectui runtime already ships (objectui#2576) so the
7+
protocol and the renderer agree:
8+
9+
- **`GlobalFilterSchema.name`** (optional string) — stable filter name used as
10+
the dashboard-variable key (readable in widget expressions as `page.<name>`)
11+
and as the key widgets reference in `filterBindings`. Defaults to `field`;
12+
`"dateRange"` is reserved for the built-in dashboard date range.
13+
- **`DashboardWidgetSchema.filterBindings`** (optional
14+
`Record<string, string | false>`) — per-widget binding from a dashboard
15+
filter name to one of THIS widget's fields: a string re-targets the filter to
16+
that field, `false` opts the widget out, absent falls back to the filter's
17+
own `field`.
18+
19+
Purely additive — existing dashboards parse unchanged. The metadata-admin
20+
dashboard inspector (objectui `dashboard-schema.ts`) derives its form from this
21+
schema via `z.toJSONSchema`, so both properties surface there automatically
22+
once objectui picks up this spec version.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): `readonly` is server-enforced on UPDATE, not a UI-only affordance (#3003)
6+
7+
The `readonly` field property was described as "Read-only in UI", which #3003
8+
proved to be exactly how integrators read it — approval/status/amount columns
9+
protected only by `readonly: true` were forged with a direct REST `PATCH`,
10+
self-approving a multi-stage approval on the released 15.0.0. Since #2948 the
11+
engine strips caller-supplied writes to statically-readonly fields from every
12+
non-system UPDATE (single-id and multi-row, symmetric with `readonlyWhen`;
13+
INSERT may still seed the column). The schema description and the field
14+
liveness ledger now state the server-side contract, and a dogfood conformance
15+
proof (`showcase-static-readonly.dogfood.test.ts` + an authz-matrix row) pins
16+
it end-to-end so it cannot silently regress to renderer-only.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
'@objectstack/runtime': patch
3+
'@objectstack/spec': patch
4+
'@objectstack/service-realtime': patch
5+
---
6+
7+
fix(security): pre-wiring identity admission for the GraphQL and realtime surfaces (#2992, ADR-0096 D4)
8+
9+
Two latent execution surfaces — neither reachable by a client today — would
10+
have fallen open the instant a real transport was wired, because both drop or
11+
lack the caller's identity. Per ADR-0096, the identity story is fixed and
12+
pinned in CI *before* wiring, not after an adversarial review:
13+
14+
- **GraphQL (surface 1 — latent context-drop, now threaded).**
15+
`handleGraphQL` passed only `{ request }` to `kernel.graphql`, dropping the
16+
resolved `ExecutionContext` — the moment a real engine resolved objects
17+
through ObjectQL it would have run context-less (security middleware falls
18+
OPEN on a missing principal = full authority). The entry point now resolves
19+
the caller identity even on the direct dispatcher-plugin route and even when
20+
`requireAuth` is off, and threads it as `options.context`;
21+
`IGraphQLService.execute` documents that implementations MUST forward it to
22+
every data-engine call. Unit-proven; the authz conformance matrix pins the
23+
threading (`graphql-identity-thread` row) so removing it goes STALE and
24+
fails CI.
25+
26+
- **realtime (surface 2 — no per-recipient authz seam, posture registered).**
27+
Delivery is a pure fan-out (subscriptions carry no principal,
28+
`matchesSubscription` filters only by object+eventTypes, the engine
29+
publishes the full `after` row), safe only while every subscriber is
30+
server-internal. The posture is now registered as an `experimental` matrix
31+
row (`realtime-delivery-authz`) stating the admission requirement
32+
(per-recipient RLS/FLS/tenant re-check on delivery, or id-only payload +
33+
client re-fetch), and transport TRIPWIRE probes turn any newly wired
34+
WebSocket/SSE/subscribe/client transport into an UNCLASSIFIED surface → red
35+
CI until the identity story ships with it. The `service-realtime` README —
36+
which advertised `authorizeChannel`/`broadcastToUser`/presence auth that do
37+
not exist — is rewritten to describe the real, trusted-internal-only
38+
surface, and the contract docs carry the admission requirement at the seam.

0 commit comments

Comments
 (0)