Skip to content

Commit c067557

Browse files
hotlongclaude
andcommitted
Merge remote-tracking branch 'origin/main' into claude/issue-20378-diff-draft-versions
Claude-Session: https://claude.ai/code/session_local_1d2a197c-c20e-4e90-9be8-413d4d432289 Co-authored-by: Claude <noreply@anthropic.com>
2 parents db05a9e + c745e2b commit c067557

29 files changed

Lines changed: 2559 additions & 747 deletions
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
---
2+
'@objectstack/rest': minor
3+
'@objectstack/runtime': patch
4+
---
5+
6+
fix(rest, runtime): the runtime dispatcher's `/meta` reads answer what `RestServer`'s answer — one list chain, one `public`-audience predicate, and the `?state=draft` read (#20320)
7+
8+
Clause-②: yes (widening) — `@objectstack/rest`'s root entry gains five value exports (`createMetaListAnswer`, `translateMetaList`, `metaRequestLocale`, `isPublicAudienceRead`, `STORED_VERSION_DOOR_POLICY`) and six type exports (`MetaListAnswer`, `MetaListAnswerSources`, `MetaListRequest`, `MetaListTranslationSources`, `MetaPublicReadRoute`, `MetaRequestHttp`), so its published surface grows; nothing it exported before is removed, renamed or narrowed. `@objectstack/runtime` publishes no new surface and stays a `patch`.
9+
10+
A host that mounts only the `${prefix}/*` catch-all (`createHonoApp`, and any
11+
adapter written on the public `HttpDispatcher` API) serves `/meta` through the
12+
runtime dispatcher. Its reads now give the same answers as `RestServer`'s
13+
`GET /meta/:type` and `GET /meta/:type/:name`. Until now a dispatcher-only host
14+
answered:
15+
16+
- **`GET /meta/app?id=crm`** — every app the caller may see, not `[crm]`. An
17+
`?id=` that matches nothing listed every app instead of an empty list.
18+
- **`GET /meta/view?object=lead`** — every view, not the lead views sorted for
19+
the switcher.
20+
- **`GET /meta/docs`** (the plural spelling) — every doc WITH its body. The
21+
content slim compared the raw segment, so it ran only for `/meta/doc`.
22+
- **any doc list** — each doc with its `translations` map and in no locale.
23+
`RestServer` collapses each doc to the request's locale.
24+
- **every translatable list** (`app`, `view`, `object`, `page`, `dashboard`,
25+
`action`, `dataset`) — untranslated labels, whatever `Accept-Language` or
26+
`?locale=` asked for, and no `Vary: Accept-Language` header.
27+
- **`GET /meta/api`** — every stored `api` declaration, including ones the
28+
endpoint matcher does not serve (their routes answer 404).
29+
- **an anonymous `GET` of a `public` book or doc** (list or item) —
30+
`401 UNAUTHENTICATED`. `RestServer` serves it (ADR-0046 §6.7).
31+
- **`GET /meta/:type/:name?state=draft` from a caller who may read drafts** —
32+
the ACTIVE item. It should be the pending draft, whole for a caller who may
33+
save the app and pruned per caller for everyone else, or `404 NO_DRAFT` when
34+
nothing is pending. A caller who may not read drafts is still answered the
35+
plain read, byte for byte.
36+
37+
**What changed.** The list route's whole post-read chain moved out of
38+
`RestServer` into `createMetaListAnswer` in `@objectstack/rest`, unchanged. That
39+
chain is the `api` served-set face, the per-caller list gate, `?id=`,
40+
`?object=`, the doc locale collapse and content slim, the transport's own
41+
object mask, and the translation. Every exit of the dispatcher's list branch
42+
now hands its answer to that same function. The anonymous gates on both
43+
transports ask one exported predicate, `isPublicAudienceRead`. It admits only
44+
`GET` reads of book and doc, so every other type keeps the anonymous deny, and
45+
the §6.7 audience gate still refuses `org` and `{ permissionSet }` audiences.
46+
The dispatcher's `?state=draft` read runs the exported
47+
`STORED_VERSION_DOOR_POLICY`, the constant `RestServer`'s draft branch runs.
48+
49+
`RestServer`'s own answers are unchanged: the move is a refactor on that side,
50+
and every existing REST test passes unedited.
51+
52+
New exports from `@objectstack/rest`: `createMetaListAnswer`,
53+
`translateMetaList`, `metaRequestLocale`, `isPublicAudienceRead`,
54+
`STORED_VERSION_DOOR_POLICY` and their types. Nothing is removed or renamed.

‎.changeset/20381-adhoc-cube-request-scope.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,5 @@
77
Clause-②: no
88

99
- **What changes**: both ad-hoc doors — `query()` (`POST /api/v1/analytics/query`) and `generateSql()` (`POST /api/v1/analytics/sql`) — resolved the query's cube and recorded what `ensureCube` minted straight into the shared registry, ahead of the admission check. A request refused `PERMISSION_DENIED` still left the cube it inferred for the refused object in the registry, and a suffix measure a caller named on a registered cube (`<field>_sum`, `<field>_count_distinct`, …) was appended to that cube for every later reader, whether the request was refused or admitted. Both doors now run in the same request-local scope `queryDataset` runs in: what `ensureCube` mints stays with the call, and the admission, read scope and strategy all read it from there.
10-
- **What does not change**: every request is served as before, with the same admission, read scope, refusals, codes and statuses, and a caller-named suffix measure is still served to the caller who named it. An ADMITTED ad-hoc query over an object with no configured cube still registers the cube it inferred, as before — now only after the admission has admitted the request, and never over a cube registered under the same name in the meantime. Configured cubes and datasets registered at construction (`AnalyticsServiceConfig.cubes` / `datasets`) are untouched.
11-
- **What `getMeta()` lists, the one observable difference**: `getMeta()` and `GET /api/v1/analytics/meta` no longer list a cube inferred for a refused request, and no longer list a suffix measure some caller named on a registered cube — a registered cube is listed as it was registered. A cube inferred for an admitted request is still listed.
10+
- **What does not change**: every request is served as before, with the same admission, read scope, refusals, codes and statuses, and a caller-named suffix measure is still served to the caller who named it. A cube inferred for an ADMITTED ad-hoc query is no longer registered either; the separate #20381 entry that retires inferred-cube registration describes that change. Configured cubes and datasets registered at construction (`AnalyticsServiceConfig.cubes` / `datasets`) are untouched.
11+
- **What `getMeta()` lists, the one observable difference**: `getMeta()` and `GET /api/v1/analytics/meta` no longer list a cube inferred for a refused request, and no longer list a suffix measure some caller named on a registered cube — a registered cube is listed as it was registered.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
"@objectstack/service-analytics": patch
3+
---
4+
5+
A cube `AnalyticsService` infers for an ad-hoc `query()` or `generateSql()` request is no longer registered in the service-wide cube registry, even when the request is admitted, so `getMeta()` and `GET /api/v1/analytics/meta` list configured cubes only (#20381).
6+
7+
Clause-②: no
8+
9+
- **What changes**: an ad-hoc request naming an object that no cube is configured over (`POST /api/v1/analytics/query`, `POST /api/v1/analytics/sql`) is still served from a minimal cube inferred from that request's own members. That cube now lives only in the request that inferred it, like a suffix measure a caller appends to a configured cube. Before, an admitted request left it in the shared registry, so `getMeta()` listed it to every caller, including callers who may not read the object, together with the member names the first caller used. Its contents depended on who had queried what since boot, and it was lost on restart.
10+
- **What does not change**: every request is served as before, with the same answer, admission, read scope, refusals, codes and statuses. A repeat request for the same object infers the cube again, through the same existence and source-field checks, and gets the same answer. Configured cubes (`AnalyticsServiceConfig.cubes`) and datasets registered through `registerDataset` (the constructor's `datasets`, or an embedder) are registered and listed as before, and they are now the registry's only writers.
11+
- **What to do**: nothing, unless something reads `getMeta()` / `GET /api/v1/analytics/meta` expecting to find a cube that only an ad-hoc query inferred. No consumer in this repository does. Author that cube explicitly (`defineCube`, or the analytics service's `cubes` config) so that it is listed, and listed the same way after a restart.
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
---
4+
5+
**plugin-auth: a refused auth setting no longer drops the other settings saved with it**
6+
7+
Clause-②: no
8+
9+
The `auth` settings pass used to go to `AuthManager.applyConfigPatch()` as ONE patch. When the
10+
manager refused one key in it, the whole patch was dropped: password policy, MFA, rate limits,
11+
session lifetime and social providers from that pass were not applied. The settings console
12+
still showed every one of them as saved, and the only trace was a `warn`.
13+
14+
Reachable examples:
15+
16+
- posture `open` (declared in stack config, or opened earlier through the console) with
17+
`auth.require_email_verification: false` stored through the console;
18+
- posture `email_domain` with `OS_AUTH_REQUIRE_EMAIL_VERIFICATION=false`;
19+
- the SCIM/admin coherence refusal on the `plugins` block, once `OS_SCIM_ENABLED` appears after
20+
the manager was constructed with `plugins.admin: false`.
21+
22+
Now the pass is applied in pieces, split where the manager can refuse. Each key that lands in
23+
the `emailAndPassword` or `plugins` block is applied on its own; every other key goes out in one
24+
application the manager does not validate. `mfa_required` stays one piece with the `twoFactor`
25+
plugin it turns on, so MFA is never enforced without its enrollment endpoints. The manager's
26+
verdict on each piece is the verdict; no validation moved into the plugin.
27+
28+
A refusal is logged once, at `error`, naming the key:
29+
`[auth] auth settings REFUSED (auth.require_email_verification) — the standing runtime value
30+
keeps ruling …`, followed by the manager's own message, which carries the remedy. Every other
31+
setting in the pass still applies. A pass that fails as a whole, such as a settings namespace
32+
that cannot be read, is also logged at `error` (`[auth] auth settings NOT APPLIED — …`).
33+
34+
The old `Auth: failed to apply auth settings:` warning is gone. A log alert that matched it
35+
should match `[auth] auth settings REFUSED` and `[auth] auth settings NOT APPLIED` instead.

0 commit comments

Comments
 (0)