Skip to content

Commit dc055ca

Browse files
committed
Merge origin/main into claude/dispatcher-handler-ready-1odd28 — reconcile with #4082 (#4058 step 1)
#4082 landed the classification half of #4058 while this branch was open, and did it better: `DEV_STUB_SELF_INFO` is one reviewable table, it covers the kernel's OWN fallbacks too (the `_fallback: true` marker no consumer recognized — filed from this branch as #4089, now fixed at the source), it drops the blanket `_dev: true` from plugin-dev rather than keeping it as a provenance tag, and it sets `handlerReady: false` on the surfaceless `degraded` fallbacks (`cache`/`queue`/`job`/`realtime`) — a refinement this branch missed. So this branch keeps only what #4082 explicitly left open: step 2, the gate. Dropped from this branch as superseded: - the per-stub `devDegraded`/`devStub` markers and their tests (main's table wins); - retiring the `automation` / `notification` / `ai` stubs. Step 1 classes them `stub`, so the gate already answers their HTTP surface as an empty slot without deleting anything — verified end to end below. Whether the fabricating class should be registered at all is the wider evaluation in #4093 (A tier), which is where that deletion belongs alongside `data`/`auth`/`security.*`. Kept: `isServiceServeable` + the six domain gates, the discovery `routes`/ `features` alignment, the metadata-protocol route suppression, their tests, and the ADR/docs/changeset text — the ADR annotation now reads as step 2 on top of step 1 instead of re-claiming the classification. The gate is compatible with step 1's table by construction, because it reads `handlerReady` and not `status`: `file-storage` / `i18n` / `metadata` / `workflow` declare `degraded` with `handlerReady` defaulting to true and keep serving, while the surfaceless `degraded` fallbacks have no dispatcher domain to gate. Verified against a real plugin-dev slate after the merge: `automation` / `notification` / `ai` are registered and classed `stub` ⇒ no route advertised, `GET /automation` and `GET /notifications` unhandled, `POST /ai/chat` 404, and `GET /ai/agents` still answers `{ agents: [] }`; `file-storage` / `i18n` / `workflow` / `metadata` keep their routes and serve. runtime 923, plugin-dev 8, objectql 1183, metadata-protocol 99, core 415 all pass. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018UNGqBQcdJ2RYHtWgntJ9B
2 parents f851709 + ea24593 commit dc055ca

97 files changed

Lines changed: 4332 additions & 877 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: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
"@objectstack/plugin-auth": patch
3+
---
4+
5+
fix(plugin-auth): the auth catch-all yields paths better-auth does not own (#4088)
6+
7+
`registerAuthRoutes` mounts `rawApp.all('${basePath}/*')` over the whole auth
8+
namespace (`/api/v1/auth` by default), and that handler was **terminal**: it
9+
returned better-auth's response unconditionally, including the 404 better-auth
10+
produces for a path it does not implement. Any other plugin's route under that
11+
prefix was therefore reachable only if it happened to register **first** — Hono
12+
runs handlers matching a path in registration order and the first to return a
13+
Response wins.
14+
15+
That put a load-bearing surface at the mercy of `kernel.use()` order.
16+
`@objectstack/plugin-hono-server` mounts `/auth/me/permissions` and
17+
`/auth/me/localization` from its own `kernel:ready` hook; objectui's entire
18+
permission layer reads the former and `core`'s auth gate allow-lists the latter
19+
as an endpoint a gated user must still reach. Register `AuthPlugin` before
20+
`HonoServerPlugin` and all of it silently 404s.
21+
22+
A 404 from better-auth now means "this path is not mine" and the catch-all yields
23+
to whatever else matched, in either registration order. Deliberately narrow:
24+
25+
- **Only 404 falls through.** 401/403 are real better-auth answers, not
26+
disclaimers of ownership.
27+
- **Precedence still favours the namespace owner.** better-auth wins every path
28+
it implements; only its leftovers are up for grabs.
29+
- **The unclaimed-path wire shape is unchanged.** When nothing downstream
30+
answers, better-auth's own 404 is returned verbatim rather than Hono's
31+
`404 Not Found`.
32+
33+
No configuration changes and no new routes. The only behavioural difference for
34+
an existing deployment is that a route another plugin mounts under
35+
`/api/v1/auth/*` now answers regardless of plugin order — previously it answered
36+
only in the lucky order.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/formula": patch
4+
"@objectstack/core": patch
5+
---
6+
7+
fix(formula,spec,core): the RLS write-side `check` evaluator honours calendar-day upper bounds (ADR-0053 D-D)
8+
9+
`@objectstack/formula`'s `matchesFilterCondition` — the evaluator behind RLS
10+
write-side `check` policies (ADR-0058 D4) — compared a bare `YYYY-MM-DD` `$lte`
11+
bound literally. On a `datetime` post-image that meant a policy of the shape
12+
`{ signed_on: { $lte: '{today}' } }` **denied every write made after 00:00**:
13+
the write-side twin of the read-side data loss #3777 fixed, and the last of the
14+
platform's filter backends that disagreed about what a bare day means as a
15+
bound.
16+
17+
`$lte` and a `$between` max now evaluate half-open against the next calendar
18+
day, matching the SQL compiler, the memory and mongo drivers, and the analytics
19+
preview evaluator. Unchanged, per the same semantics table: full-ISO bounds keep
20+
exact-instant semantics, `$gte`/`$gt`/`$lt` keep their midnight anchoring, and a
21+
plain `YYYY-MM-DD` value compares identically (string ordering makes the two
22+
forms equivalent). The evaluator stays fail-closed on a null bound.
23+
24+
**Where the rule now lives.** `nextUtcCalendarDay` moved from
25+
`@objectstack/core` to `@objectstack/spec/data` — beside `date-macros.zod.ts`,
26+
whose vocabulary it interprets. `formula` cannot depend on `core`, and a second
27+
copy of the rule is exactly the divergence #3777 catalogued; `spec` is the one
28+
package all six consumers already depend on, so this adds no dependency edge.
29+
30+
No import changes are required: `@objectstack/core` re-exports the symbol, so
31+
existing `import { nextUtcCalendarDay } from '@objectstack/core'` keeps working.
32+
New code should prefer `@objectstack/spec/data`.
Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
---
2+
---
3+
4+
Tests only — no package changes, nothing to release.
5+
6+
Reconciles the designer form against the Zod for the region-bearing flow nodes
7+
(#4045). `loop` / `parallel` / `try_catch` each carry two descriptions of the same
8+
config — a hand-written `configSchema` on the descriptor and a Zod schema in
9+
`control-flow.zod.ts` — and nothing compared them. #4064 pinned the shape of the
10+
first; this pins the relationship, so neither side can gain or lose a key without
11+
the other noticing.
12+
13+
The two are **not** merged into one source, because measurement says that does not
14+
work here: generating from the Zod emits 9–17× more schema at +9 levels of depth
15+
(`loop` 597 → 5,537 chars, `parallel` 294 → 5,112, `try_catch` 737 → 10,739), with
16+
no `$defs`/`$ref``FlowNodeSchema` is inlined at every region key, so a loop body
17+
would arrive as the whole node/edge definition instead of the opaque array the
18+
designer needs for a canvas-edited sub-graph. A projection pruning ~90% back off
19+
would leave three things to maintain instead of two.
20+
21+
So the divergence is legitimate, and what is enforced is that it stays declared: a
22+
`DELIBERATELY_SHALLOW` ledger names each shallow key with a reason, and every entry
23+
must name a key both sides still declare, so it cannot rot.
24+
25+
Compares key sets rather than generated shapes on purpose — generating needs
26+
`z.toJSONSchema`, and `service-automation` does not depend on `zod`. The Zod's key
27+
set is reachable via `schema.shape` without that dependency, and the key set is
28+
where the drift that matters shows up.
29+
30+
Verified to bite in all three directions: a Zod key the form does not offer, a form
31+
key the Zod rejects, and a ledger entry pointing at a renamed key each turn the
32+
suite red with the offending name in the message.

.changeset/dispatcher-handler-ready-class-wide.md

Lines changed: 0 additions & 16 deletions
This file was deleted.
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
---
2+
'@objectstack/runtime': patch
3+
'@objectstack/metadata-protocol': patch
4+
---
5+
6+
Gate every dispatcher service domain on `handlerReady` instead of on slot occupancy (#4058 step 2).
7+
8+
#4000 made the `/analytics` domain execute ADR-0076 D12's third conclusion ("consumers treat only `handlerReady: true` as a real capability"); every other domain still gated on "is a service registered", so a self-declared stub occupying `automation` / `notification` / `ai` / `file-storage` / `i18n` was called like a real implementation and its fabricated answer went out as a 200. Step 1 (#4082) made the two kinds of dev implementation distinguishable; this is the gate that reads the distinction.
9+
10+
- The `/analytics`, `/automation`, `/notifications`, `/ai`, `/storage` and `/i18n` domains, the route-mount gate, discovery's `routes`/`features`, and the metadata-protocol builder's route advertisement now share one predicate (`isServiceServeable`): a slot whose occupant self-declares `handlerReady: false` is answered exactly as an empty slot is — the domain's existing 404, or the explicit 501 `/storage` and `/i18n` use. One predicate, so what is advertised and what is served cannot disagree.
11+
- `handlerReady`, not `status`, is the test. An implementation that declares `degraded` defaults to `handlerReady: true` and keeps serving — which is why the in-memory `file-storage` and `i18n` implementations are unaffected.
12+
- `discovery.services.*` stays presence-gated: a registered stub still reports `{ enabled: true, status: 'stub', handlerReady: false }` (with no `route`), which says strictly more than collapsing it to `unavailable` would.
13+
- `/ai` improves for the stub case: an occupied-but-unserveable slot used to fall through to a 503 "AI service routes not yet initialized" and lose the `GET /ai/agents` empty-list answer the console polls for on every navigation. Both are restored.
14+
15+
No change for a host whose services are real implementations. If you register your own stub under one of those six slots and relied on the dispatcher calling it, either drop the `handlerReady: false` self-declaration (declare `degraded` if it genuinely serves) or install the real service. Not gated, deliberately: `/data`, `/meta`, `/auth` and the security path — their dev stubs back the dev stack's own core loop, and gating them would 404 the dev stack itself.
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
'@objectstack/core': patch
3+
'@objectstack/plugin-dev': patch
4+
'@objectstack/runtime': patch
5+
---
6+
7+
Every in-memory fallback and dev stub now self-describes with the standard `__serviceInfo` descriptor, classified by what it actually is (#4058 step 1).
8+
9+
ADR-0076 D12 gave services one way to say "I am not the real thing", but the producers never converged on it:
10+
11+
- The kernel's own fallbacks (`createMemoryCache` / `Queue` / `Job` / `I18n` / `Metadata`) carried `_fallback: true` — a marker **no** consumer recognized, `readServiceSelfInfo` included — so both discovery builders reported them as fully `available`.
12+
- `plugin-dev` marked all of its implementations with the same `_dev: true`, normalized to `status: 'stub', handlerReady: false`. That declared a working in-memory search index exactly as fake as an AI stub returning invented text.
13+
14+
Both now carry `__serviceInfo`, split by a rule that holds across the whole set:
15+
16+
- **`degraded`** — really does the work, with reduced capability: `cache`, `queue`, `job`, `file-storage`, `search`, `i18n`, `metadata`, `workflow`, `realtime`. Its answers are true answers; the `message` names what is missing (no persistence, no scheduling timer, no state-machine validation, …).
17+
- **`stub`** — the answer is fabricated: `ai`, `automation`, `notification`, `data`, `auth`, `security.permissions`, `security.rls`, `security.fieldMasker`. Never to be mistaken for a capability.
18+
19+
`handlerReady: false` is set independently wherever no HTTP handler serves the slot (`cache` / `queue` / `job` / `realtime`, and every `stub`).
20+
21+
Discovery output changes accordingly — a kernel fallback that used to report `status: 'available'` now reports `degraded` with an explanatory message. No routing, gating, or dispatch behavior changes: every dispatcher domain still resolves services exactly as before. Consumers reading `discovery.services.*` get the truth instead of a uniform claim.
22+
23+
For anything that duck-typed the old markers: `svc._fallback` / `svc._dev``readServiceSelfInfo(svc)` from `@objectstack/spec/api` (the legacy `_dev` key is still understood by that reader, so third-party stubs carrying it keep working).
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
"@objectstack/plugin-hono-server": minor
3+
---
4+
5+
fix(plugin-hono-server): stop gating the current-user endpoints behind `registerStandardEndpoints` (#4073)
6+
7+
`registerStandardEndpoints` gated two unrelated things behind one flag:
8+
9+
- **Duplicate supply** — raw `POST/GET /api/v1/data/:object` (create + read
10+
only), which `@objectstack/rest` also serves and, registering first, is what
11+
actually answers; plus `GET /api/v1/discovery` and
12+
`/.well-known/objectstack`, which the dispatcher and REST own and which this
13+
surface already cedes to them (#4018).
14+
- **Sole supply**`GET /api/v1/auth/me/permissions`,
15+
`/api/v1/auth/me/localization` and `/api/v1/me/apps`. Nothing else in the
16+
platform mounts these: neither `@objectstack/rest` nor `@objectstack/runtime`
17+
registers any `/me/*` route, the console's entire permission layer reads
18+
`/auth/me/permissions`, the console reads `/auth/me/localization` for regional
19+
defaults, and `core`'s auth gate allow-lists `/me/apps` + `/me/localization`
20+
as endpoints a gated user MUST still reach to bootstrap the remediation UI.
21+
22+
`os serve` gets all of it only because the flag defaults to `true` — the CLI
23+
constructs `new HonoServerPlugin({ port })`. So `registerStandardEndpoints:
24+
false`, whose documented job is the optional CRUD/discovery convenience surface,
25+
silently took the console's permissions and localization down with it.
26+
27+
The three current-user endpoints now register **unconditionally**, and the flag
28+
covers the duplicate half only — what its name and docs always claimed.
29+
30+
**FROM → TO.** If you set `registerStandardEndpoints: false` and worked around
31+
the missing endpoints (proxying `/auth/me/permissions` yourself, or pinning the
32+
flag to `true` purely to keep them), you can drop that workaround: the endpoints
33+
are now present either way. No route is removed and no response shape changes,
34+
so a host that left the flag at its default sees no difference. If you relied on
35+
`false` meaning "this plugin mounts no `/api/v1` routes at all", that is no
36+
longer true — it never was for `os serve`, which is the only host that shipped
37+
the flag's default.
38+
39+
Also removes three unreferenced `*_ENDPOINT_PRIORITY` constants;
40+
`DISCOVERY_ENDPOINT_PRIORITY = 900` in particular implied a route-priority
41+
mechanism that does not exist (precedence here is Hono's
42+
first-registration-wins).
Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
refactor(spec)!: retire three orphan operator vocabularies (objectui#2945 Track A)
6+
7+
An audit of every comparison/aggregation vocabulary the spec ships
8+
(objectstack-ai/objectui#2901) found the operator vocabularies had multiplied
9+
past what any code consults. Three had **no importer at all** — not in this
10+
repo, not in objectui, not in cloud — and each contradicted the vocabulary that
11+
is actually enforced. Removed rather than reconciled: a second name for one
12+
concept is how they drifted apart in the first place.
13+
14+
**`AggregationFunctionEnum`** (`shared/enums.zod.ts`). Its own doc comment
15+
claimed it was *"used across query, data-engine, analytics, field"*. It was used
16+
by nothing. `AggregationFunction` (`data/query.zod.ts`) is the vocabulary the
17+
query engine, `service-analytics`' dataset compiler and the native-SQL strategy
18+
all gate on — and the two disagreed: this one carried
19+
`percentile`/`median`/`stddev`/`variance`, that one carries
20+
`array_agg`/`string_agg`. It also exported a *type* named `AggregationFunction`
21+
while `data/query.zod.ts` exports a *value* of that name, so the two occupied
22+
the same identifier in different declaration spaces with different members.
23+
24+
**`FilterOperator`** + `EventFilterCondition` + `EventFilterSchema`
25+
(`api/websocket.zod.ts`), reached from `EventSubscriptionSchema.filters`. No
26+
runtime ever evaluated an event filter — `matchesSubscription` matches on object
27+
name and event type only (`contracts/realtime-service.ts`) — and the
28+
subscription shape the transports actually carry is the separate, deliberately
29+
unvalidated `filters: z.unknown()` on `SubscriptionEventSchema`
30+
(`api/realtime.zod.ts`). So this was a *second* modelling of event filtering
31+
that described a capability no code provided: a subscriber who set `filters`
32+
received every event regardless.
33+
34+
The `filters` **key stays**, now typed `z.unknown()` with the same
35+
NOT-YET-ENFORCED marker as its `api/realtime.zod.ts` counterpart. Retiring an
36+
object key requires a tombstone plus a conversion (ADR-0104), which is the right
37+
rule and the wrong trade here — there is no author to migrate for a shape nothing
38+
validated, and Track A is meant to carry no migration. The two subscription
39+
surfaces now describe event filtering identically, and neither implies an
40+
enforcement that does not exist. Whichever grows real filtering should lower onto
41+
`AST_OPERATOR_MAP` rather than reintroduce a vocabulary of its own.
42+
43+
**`ODataFilterOperatorSchema`** (`api/odata.zod.ts`). Nothing parses an OData
44+
`$filter` against it — `$filter` is carried as an opaque string on
45+
`ODataQuerySchema` and as the `odata` adapter template in
46+
`query-adapter.zod.ts` — and an enum mixing operators with `(`/`)` could not
47+
validate an expression anyway, since it describes tokens, not a grammar. A real
48+
implementation needs a parser, and that parser should lower onto
49+
`AST_OPERATOR_MAP` like every other entry point.
50+
51+
**Breaking, in the narrowest sense.** All three were reachable as public
52+
exports (`@objectstack/spec/shared` and `@objectstack/spec/api`), so this is a
53+
`major`. No consumer exists to break: verified by grep across framework
54+
`packages/` + `apps/`, objectui, and cloud. Nothing is *narrowed* — no accepted
55+
value stops being accepted, so no already-stored metadata or in-flight payload
56+
changes meaning. That is what made this the one track of objectui#2945 that was
57+
safe to start; narrowing `VALID_AST_OPERATORS` or retiring a
58+
`VIEW_FILTER_OPERATORS` alias is not, and remains blocked on #3948.
59+
60+
The generated artefacts move with the deletions, as the ratchets require:
61+
`json-schema.manifest.json` drops the five unpublished schemas,
62+
`authorable-surface.json` the seven keys of the two deleted objects,
63+
`api-surface.json` the eight exports, and the three reference-doc pages are
64+
regenerated.
65+
66+
Verified: full `@objectstack/spec` suite **6917 tests across 266 files**, plus
67+
`tsc --noEmit`, `check:docs`, `check:api-surface`, `check:authorable-surface` and
68+
`check:skill-docs`, all clean.

0 commit comments

Comments
 (0)