Skip to content

Commit 8ac3ddf

Browse files
baozhoutaoclaude
andcommitted
merge: main into claude/issue-3957-review-8c0daf (2nd) — regenerate the api reference
Only conflict was `content/docs/references/api/errors.mdx`, an AUTO-GEN file that both sides touched: main through #4054's authoring-key sweep, this branch through `FieldErrorSchema.label`. Took main's copy and re-ran `gen:schema && gen:docs` rather than hand-merging a generated artifact. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2 parents d7f574d + c53aa53 commit 8ac3ddf

137 files changed

Lines changed: 5765 additions & 1467 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: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
feat(spec)!: `EnhancedApiError.fieldErrors``fields`, tombstoned (ADR-0114 D4, #3977)
6+
7+
Completes ADR-0114 D4, which the field-level catalog PR (#4035) decided but left
8+
unexecuted because retiring an authorable key needs its own sequence.
9+
10+
**FROM → TO:** `EnhancedApiError.fieldErrors``EnhancedApiError.fields`. The array
11+
and its element shape are unchanged — only the property name.
12+
13+
The wire has always carried `fields`: the validators, import coercion,
14+
`validation-failure.ts`, `@objectstack/client` and the console's field-error
15+
extractor all say `fields`. `fieldErrors` was declared and emitted by nobody, so
16+
anyone reading `error.fieldErrors` was reading a field no server sent — ADR-0078's
17+
silently-inert declaration, sitting on the error envelope.
18+
19+
**The old key is tombstoned, not deleted.** `EnhancedApiErrorSchema` is not
20+
`.strict()`, so a plain removal would let a producer still writing `fieldErrors`
21+
parse clean and lose the per-field detail — a validation failure that mentions no
22+
field. Writing it now fails with the rename prescription instead
23+
(`retiredKey()`, ADR-0104).
24+
25+
**Migration:** read `error.fields`. There is nothing to run: this is a response
26+
envelope, so no stack, example or template carries the key and `os migrate meta`
27+
has no source to rewrite. The change is recorded as a semantic chain entry
28+
(`enhanced-api-error-field-errors-renamed`) with its reason and acceptance
29+
criterion, which is what reaches the generated upgrade guide and the
30+
`spec_changes` MCP tool.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@objectstack/service-analytics": patch
3+
---
4+
5+
fix(analytics): project a `timeDimensions` bucket into the result rows and fields (#4033)
6+
7+
An analytics query that buckets by `timeDimensions` alone grouped correctly —
8+
the echoed SQL read `date_trunc('month', due_date) AS "due_date"` — but the row
9+
mapper and `buildFieldMeta` both enumerated `query.dimensions` only, so the
10+
bucket never reached the caller: rows carried just the measures and `fields`
11+
never mentioned the dimension. A trend chart got N values and no x-axis. The
12+
same query written with `dimensions: ['due_date']` was unaffected, which is why
13+
it went unnoticed.
14+
15+
Grouping, row mapping and field metadata now derive the projected set from one
16+
`projectedDimensions()` helper — `dimensions` plus every *granular*
17+
`timeDimensions` entry not already among them. A `timeDimensions` entry without
18+
a granularity contributes only its `dateRange` predicate and stays out of the
19+
projection, so no phantom column is declared.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
refactor(spec)!: the #3896 close-out sweep — fourteen inert authoring keys leave the surface
6+
7+
The enforce-or-remove worklist across the remaining metadata types, each key
8+
tombstoned at its schema with the prescription (`retiredKey`) and stripped by
9+
a protocol-17 conversion (`os migrate meta` rewrites sources):
10+
11+
- **action** `shortcut` / `bulkEnabled` — no keydown path ever dispatched a
12+
shortcut; the multi-select toolbar reads the view's `bulkActions`.
13+
- **flow** `active` / `template`, node `outputSchema`, errorHandling
14+
`fallbackNodeId``active: false` never stopped a flow (`status` is the
15+
enforced lifecycle; the default even read as disabled while the engine
16+
treated unset as enabled); faults route via per-node fault edges.
17+
- **view** list `responsive` / `performance`, form `defaultSort` / `aria`
18+
no renderer read any of them. List `aria`/`data` stay live, and **form
19+
`data` survived the sweep**: the removal attempt broke the build —
20+
`defineForm` writes `data.provider='schema'` onto every metadata form —
21+
which re-verified the entry; its ledger verdict is corrected instead.
22+
- **dashboard** `aria` / `performance`, widget `performance` (+ the orphaned
23+
`PerformanceConfigSchema`) — no renderer applied them; virtual scrolling is
24+
the live top-level `virtualScroll`.
25+
- **agent** `knowledge` (+ `AIKnowledgeSchema`) — declaring sources/indexes
26+
never scoped retrieval: `search_knowledge` takes `sourceIds` from the LLM's
27+
tool-call arguments. The protocol-17 `topics``sources` rename is absorbed
28+
into the removal pre-release.
29+
- **skill** `triggerPhrases` — phrases were never matched; activation is
30+
`triggerConditions` ∩ the agent's `skills[]` allowlist.
31+
32+
Docs-shaped annotation fields (`hook.label`/`description`, `flow.description`)
33+
are deliberately KEPT and so noted in the ledger — they document intent for
34+
the next reader and are exempt from enforce-or-remove. The stale report
35+
`aria`/`performance` ledger entries (schema already clean) are deleted as
36+
hygiene.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@objectstack/rest": patch
3+
---
4+
5+
fix(rest): give the bare 501 error exits a machine `code` (#4067)
6+
7+
Most REST error exits already carry a typed `code` (`VALIDATION_FAILED`,
8+
`BATCH_NOT_ATOMIC`, `BATCH_TOO_LARGE`, `PERMISSION_DENIED`), and the clone /
9+
search 501s already answer `{ error, code: 'NOT_IMPLEMENTED' }`. Four 501 exits
10+
still returned a bare `{ error: '<string>' }` with no code, so a client could
11+
only key on the prose:
12+
13+
- the cross-object transactional batch route (`POST {basePath}/batch`) when the
14+
runtime has no `transaction()` — the last untyped exit on that route, whose
15+
siblings (`BATCH_NOT_ATOMIC`, `VALIDATION_FAILED`, the `enforceBatchSize`
16+
`BATCH_TOO_LARGE`) were already typed by the #3897 / #3933 / #3939 line;
17+
- the two `saveMetaItem`-unsupported exits;
18+
- the UI-view-resolution-unsupported exit.
19+
20+
Each now carries `code: 'NOT_IMPLEMENTED'`, matching the clone / search 501s.
21+
Additive only — the `error` message is unchanged and no status changes — so
22+
existing clients are unaffected; new ones can branch on the code.
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
"@objectstack/driver-memory": patch
3+
"@objectstack/driver-mongodb": patch
4+
---
5+
6+
fix(driver-memory,driver-mongodb): `Field.datetime` has one storage form per driver (#4047)
7+
8+
The non-SQL counterpart of ADR-0053 D-B (#3912). Both drivers let the writer
9+
decide a datetime value's runtime type, and both compare across types by type
10+
bracket rather than by value — so a string comparand never matched a `Date`
11+
value, in either direction, for **every** operator including `$gte`.
12+
13+
A datetime column genuinely held both forms: the drivers' own
14+
`created_at`/`updated_at` defaults bind a `Date` (mongo) or an ISO string
15+
(memory), while REST/JSON writes, relative-date tokens and `initialData`
16+
fixtures supply the other. A dashboard date window therefore answered with
17+
whichever half happened to match the comparand's type — on MongoDB, where
18+
`created_at` is a BSON `Date` and dashboard bounds are strings, that meant
19+
**no rows at all**, which is worse than the final-day loss #3777 fixed.
20+
21+
Each driver now has one canonical form, applied on write and to every filter
22+
comparand:
23+
24+
| Driver | `datetime` | `date` |
25+
|---|---|---|
26+
| `driver-mongodb` | BSON `Date` — the dialect's native instant, its `timestamptz` | `YYYY-MM-DD` text |
27+
| `driver-memory` | canonical UTC ISO text (sorts chronologically under the string comparison mingo performs; survives JSON persistence) | `YYYY-MM-DD` text |
28+
29+
Both learn their temporal fields from `syncSchema`, so an object that was never
30+
declared is left exactly as written — the drivers do not guess types from
31+
values. `driver-memory` additionally converges rows already in the table when
32+
the schema arrives, which catches `initialData` fixtures and anything a
33+
persistence adapter restored (the in-memory analogue of
34+
`backfillCanonicalDatetimes`, and idempotent like it).
35+
36+
`Field.date` deliberately stays timezone-naive text on both — converting it to
37+
an instant would invent a midnight and re-couple it to a zone. The
38+
calendar-day bound semantics from #3777/#4042 are unchanged and now compose
39+
with the converged storage: the whole-day rewrite runs on the calendar string
40+
first, and only the resulting bound is converted to the storage form.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
"@objectstack/plugin-hono-server": patch
3+
---
4+
5+
fix(plugin-hono-server): compute the standalone discovery `routes` from real registrations, and cede to the real owner (#4018)
6+
7+
`registerStandardEndpoints` served a **fully static** discovery: a hardcoded
8+
`routes` table listing `auth` / `packages` / `analytics` / `workflow` /
9+
`automation` / `ai` / `notifications` / `i18n` / `storage` / `ui` regardless of
10+
what the host actually mounted. A standalone Hono deployment therefore
11+
advertised ten route families and 404'd on every one no plugin bridged — the
12+
"advertise a route that doesn't exist" class ADR-0076 D12 exists to kill, and
13+
the reason this surface disagreed with the two real discovery builders
14+
(`HttpDispatcher.getDiscoveryInfo`, `metadata-protocol`'s `getDiscovery`), which
15+
both compute per service at runtime.
16+
17+
Two changes, no new discovery implementation to keep in sync:
18+
19+
- **Single owner (D11 / OQ#9).** When `@objectstack/rest` or the runtime
20+
dispatcher is on the kernel, this surface no longer registers
21+
`${prefix}/discovery` — that plugin owns it. Both register during plugin
22+
`start()`, i.e. before this `kernel:ready` hook, and Hono is
23+
first-registration-wins, so they already shadowed this handler in every
24+
composed deployment: the cede changes no served payload, it removes a third
25+
one nobody read. `/.well-known/objectstack` is ceded to the dispatcher only
26+
(REST never registers it), so a REST-without-dispatcher host keeps the
27+
redirect.
28+
29+
- **Computed, not hardcoded (D12).** When this surface does own `/discovery`,
30+
`routes` is derived per request from the app's live route table: a family is
31+
advertised iff a route is really registered at or under its base path. A
32+
wildcard mounted *above* the base (global `/*` middleware, `/api/v1/*`) does
33+
not count as a mount.
34+
35+
**What changes for you.** On a standalone `HonoServerPlugin` host (no REST, no
36+
dispatcher), `GET /api/v1/discovery` now omits every family nothing mounts —
37+
most visibly `routes.metadata`, since `/api/v1/meta` ships with
38+
`@objectstack/rest` / the dispatcher. Clients that read a route out of
39+
discovery and call it stop getting a 404; `@objectstack/client` falls back to
40+
the conventional path for any omitted key, so `client.connect()` is unaffected.
41+
Composed deployments (`os serve`, cloud) are unchanged — the dispatcher's
42+
service-aware discovery was already the one being served.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/plugin-sharing": patch
3+
---
4+
5+
fix(sharing): the criteria-less-rule warn is once per rule per process, plus one boot aggregate (#3929 follow-up)
6+
7+
Pre-dedup the fail-closed evaluator warned on EVERY pass — per evaluation and
8+
per reconciled write — so one legacy criteria-less rule could dominate a
9+
deployment's log. Enforcement is unchanged (such a rule still matches
10+
nothing and its grants are revoked on reconcile); the warn now fires once
11+
per rule per process, and the boot backfill emits a single operator-facing
12+
aggregate (count + rule names + the fix: repair the criteria or set
13+
active: false).
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
---
2+
---
3+
4+
Tests only — no package changes, nothing to release.
5+
6+
Pins the published `configSchema` of the `loop` / `parallel` / `try_catch`
7+
descriptors, which had no assertions at all: the designer's input for the whole
8+
control-flow trio was unguarded (#4045 step A).
9+
10+
The assertions capture *intent*, not just current bytes. All three publish a form
11+
description that is deliberately **shallower and looser** than its Zod counterpart
12+
in `control-flow.zod.ts`:
13+
14+
- region keys (`loop.body`, `parallel.branches[].nodes/edges`, `try_catch.try/catch`)
15+
publish `{ type: 'array' }` with **no `items`**, so the designer treats a sub-graph
16+
as opaque — it is edited on the canvas, not in a property form. The Zod is
17+
`FlowRegionSchema` (`z.array(FlowNodeSchema)`), which would generate the entire
18+
FlowNode/FlowEdge definition nested there;
19+
- `collection` is `z.string().min(1)` and `iteratorVariable` is `.default('item')`
20+
in Zod, yet the form publishes neither `minLength` nor `default`.
21+
22+
This matters for the rest of #4045: the plan (and the issue) assumed these three
23+
were redundant copies awaiting de-duplication by a single Zod source. They are not
24+
— they are a second artifact with a different job, and the differences are
25+
deliberate. With these pinned, any move toward a generated `configSchema` either
26+
keeps them green or has to state explicitly why the designer contract changed.
27+
28+
Verified to bite: simulating what a naive `z.toJSONSchema` swap would emit (deep
29+
`items` under `body.nodes`, plus `minLength`/`default`) turns both new `loop` tests
30+
red.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
feat(spec)!: retire `BatchOptions.validateOnly` — a dry-run flag that was never implemented (#4052)
6+
7+
`BatchOptions.validateOnly` promised a dry-run — "validate records without
8+
persisting changes" — but no batch surface ever read it. `updateManyData`,
9+
`deleteManyData` and `batchData` all persist regardless, so a caller sending
10+
`options.validateOnly: true` to PREVIEW a mutation got it executed. That is the
11+
dangerous direction of "declared ≠ enforced": a flag lying about a data-safety
12+
guarantee, not merely an inert no-op.
13+
14+
There is no dry-run today. Rather than back-fill an implementation to match a
15+
promise nothing kept — a real no-commit batch has its own design space (cascade
16+
and constraint semantics under rollback, a response contract that reports each
17+
row's would-succeed verdict) — the key is retired so it can be reintroduced
18+
deliberately when there is a real need.
19+
20+
**Breaking change.**
21+
22+
- `BatchOptions.validateOnly` is a retired key. It is tombstoned (`retiredKey`)
23+
in `BatchOptionsSchema`, so authoring it now fails with a fix-it prescription
24+
rather than being silently stripped (the ADR-0104 / #3733 quiet-failure class).
25+
The `BatchOptions` type's `validateOnly` becomes `never`.
26+
- The retirement is HTTP-only (the key never appeared in stored stack metadata),
27+
so it is recorded as a semantic migration on the protocol-18 chain step
28+
(`batch-options-validate-only-retired`) — a TODO for API callers, not a stack
29+
conversion.
30+
31+
**Migration.** Stop sending `options.validateOnly` on `/batch`, `/updateMany`
32+
and `/deleteMany`. It never previewed anything; removing it changes no behaviour.
33+
If you need to validate a batch without writing, follow #4052 so a real
34+
no-commit preview can be designed.
35+
36+
Also fixes a dangling documentation reference: the `/createMany` route
37+
registration named `requestSchema: 'CreateManyRequestSchema'`, a schema no
38+
module ever exported — pointed at the real `CreateManyDataRequestSchema`.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/plugin-dev': patch
3+
'@objectstack/runtime': patch
4+
---
5+
6+
Retire the dev-mode `analytics` stub, and make the dispatcher gate `/analytics` on `handlerReady` rather than on service presence (#4000).
7+
8+
Retiring the degraded analytics shim (#3891) made an empty `analytics` slot the honest signal: `/api/v1/analytics/*` 404s and discovery reports `unavailable`. `plugin-dev` refilled that slot with a stub, which re-created the retired shape in dev mode — the dispatcher gated on "is a service registered", so the stub was called like a real engine and its empty result came back as a 200.
9+
10+
- `plugin-dev` no longer registers an `analytics` dev stub; the slot stays empty (`NO_DEV_STUB_SERVICES`). Every other dev stub is unchanged.
11+
- The `/analytics` domain, its route-mount gate, and discovery's `routes`/`features` now share one predicate (`isAnalyticsServiceServeable`): a service that self-declares `handlerReady: false` (ADR-0076 D12 — `__serviceInfo`, or plugin-dev's legacy `_dev: true`) is treated as an empty slot. A `degraded` implementation that genuinely serves requests keeps serving; `discovery.services.analytics` still reports a registered stub as `status: 'stub'`, which says more than `unavailable` would.
12+
13+
FROM → TO for dev setups that relied on the stub answering `POST /api/v1/analytics/query` with `{ rows: [], fields: [] }`: install the real engine — `@objectstack/service-analytics` runs an InMemory strategy and needs no database of its own. Nothing else changes; hosts that already install it (including `os serve`, where `analytics` is in `ALWAYS_ON_CAPABILITIES`) are unaffected.

0 commit comments

Comments
 (0)