Skip to content

Commit c9e6463

Browse files
committed
Merge origin/main into claude/issue-20515-orgless-grants-global-only
Claude-Session: https://claude.ai/code/session_01N8TPEsoJxPsdSdNKGnNGEN Co-authored-by: Claude <noreply@anthropic.com>
2 parents 8b0826f + 31d281d commit c9e6463

69 files changed

Lines changed: 3007 additions & 466 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: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
`PackageInstallBodySchema`'s docblock records its measured residual as closed: the install door answers every body the declaration refuses with `400`, not `201`
6+
7+
Clause-②: no
8+
9+
The docblock listed the bodies `POST /api/v1/packages` answered differently
10+
from the declaration: a manifest with no `type`, unknown keys on either body
11+
form, a string-typed `enableOnInstall` / `overwrite`, install options spelled on
12+
the bare form, and (in the other direction) a whitespace-only `id`. It still said
13+
the door answers `201` to the first four. Since the door parses its whole body
14+
through `PackageInstallBodySchema` (#20218), it answers each of them `400` /
15+
`VALIDATION_ERROR` and installs nothing. The whitespace-only `id` was already
16+
refused by both, because `ManifestSchema.id` carries `MANIFEST_ID_PATTERN`.
17+
18+
The section now records every class as closed, names the door-side pin for
19+
each, and says what the declaration's parsed value still does not describe:
20+
the door stores the manifest it was SENT, so parse-time defaults (`scope`,
21+
`defaultDatasource`) are not stored, and an unknown key nested in a manifest
22+
block the declaration leaves in strip mode is stored as sent.
23+
24+
Two more sentences are corrected. The bare-form paragraph said the runtime's
25+
two door drives post a manifest with no `type`; both have carried
26+
`type: 'app'` since #20218 and parse green. The `enableOnInstall` docblock said
27+
the door "reads the raw body"; it reads the key off the parsed wrapped request.
28+
29+
⛔ No behaviour changes. No schema, accept set, export or runtime code moves.
30+
31+
**Why this carries a changeset and not `skip-changeset`.** `@objectstack/spec`'s
32+
`files[]` ships `src/**/*.zod.ts` verbatim, and the `PackageInstallBodySchema`
33+
docblock is also emitted into `dist/api/index.d.ts` and `dist/api/index.d.mts`.
34+
The published content changes, even though no line of code does.
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
fix(spec): `os migrate meta` guidance for the `field-*`, `export-*`, `api-*`, `dataset-*`, `hook-*` and `metadata-*` migration entries states each lesson in words instead of citing tracker numbers
6+
7+
Clause-②: no
8+
9+
The ADR-0087 semantic entries of the `field-*` family (the runtime `field` write door, the
10+
`maxLength` / `minLength` / `scale` / `precision` refusals, `scale` on a currency field,
11+
`multiple` on a type that holds one value, and predicates that read through a reference),
12+
the `export-*` family (the export permission axis, the eight constraint keys retired from
13+
`ExportFieldMeta` and the retired export-job API family), the `api-*` family (the runtime `api` write door,
14+
the split API entry and two duration keys renamed with their unit), the `dataset-*` family
15+
(the aggregate × field-type refusals and the nested-relation list refused at save), the
16+
`hook-*` family (the retired hook-session `roles` and the two `registerHook` refusals) and
17+
the `metadata-*` family (the retired customization protocol, the re-partitioned endpoint
18+
switches, the metadata-manager cache keys and the retired `additionalTypes`) are printed by
19+
`os migrate meta` as the header, `why:` and `verify:` lines of a manual change. Their text
20+
sent the reader to issue-tracker, decision-batch and ruling-record numbers — some of which
21+
no longer resolve, and some in another repository — for what a ruling, measurement or fix
22+
had decided; it now says what was decided, in the sentence being read. ADR ids are kept.
23+
24+
Text only: no entry id, `from` / `to`, conversion or matching logic changes, and the chain
25+
rewrites exactly what it rewrote before. One entry's `surface` (the header line of
26+
`dataset-measure-aggregate-field-type-refused`) drops the two tracker numbers it carried and
27+
names nothing else differently. The generated migration registry, `spec-changes.json` and
28+
the protocol upgrade guide carry the same text.
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
---
2+
'@objectstack/driver-sql': minor
3+
'@objectstack/driver-turso': minor
4+
'@objectstack/driver-memory': minor
5+
'@objectstack/driver-mongodb': minor
6+
'@objectstack/formula': minor
7+
'@objectstack/objectql': minor
8+
'@objectstack/spec': minor
9+
---
10+
11+
feat(drivers,formula,objectql): the engine's filter faces answer the staged `$empty` operator (#20444)
12+
13+
Clause-②: yes (widening)
14+
15+
`$empty: true | false` is declared by `@objectstack/spec` (`FieldOperatorsSchema`) with a per-type meaning: a text-like field is empty when it is null or `''`, a multi-value field (multiselect, checkboxes, tags, or a select / radio / lookup / user / file / image with `multiple: true`) when it is null or `[]`, and every other type only when it is null. `$empty: false` is the exact complement. Until now every face in this list refused it (`INVALID_FILTER` / 400), except `matchesFilterCondition`, which answered `false` for every record. **A driver or evaluator called directly now answers it:**
16+
17+
- **By the field's declared type**, through the spec's one expansion (`expandEmptyOperator`): `driver-sql`'s filter compiler (and so `driver-sqlite-wasm` and `driver-turso`'s local transport, which inherit it), `driver-turso`'s remote transport, `driver-memory`'s query path (`find` / `count` / `update` / `delete`) and `driver-mongodb`'s `translateFilter` (its `find`, its aggregate `$match`). The declaration is the one each driver already receives — `initObjects` / `registerObjectMetadata` / `registerExternalObject` on the SQL family, `syncSchema` on the others. On SQL a multi-value field's empty list is tested as stored JSON per dialect (SQLite `json_array_length` behind a `json_valid` guard, PostgreSQL a `jsonb` comparison, MySQL `JSON_LENGTH`), never as an equality comparand.
18+
- **By value** — null, a missing value, `''` and `[]` are empty (`isEmptyFilterValue`) — on the faces that read no field declaration: `@objectstack/formula`'s `matchesFilterCondition` (the RLS write-side `check`), `driver-memory`'s reference matcher, and `@objectstack/objectql`'s `having` and per-aggregation `filter`. In `having`, a `count` or `sum` holding `0` is not empty.
19+
20+
**Refused, never guessed** (`INVALID_FILTER` / 400): `$empty` on a field whose declaration the driver does not hold (a table built outside its registration, a builtin column such as `id`, a field with no `type`, or `translateFilter` / `RemoteTransport` used standalone without a declaration), a multi-value field on a SQL dialect the driver does not model, and a flag that is not a boolean. `driver-memory`'s analytics (cube) face refuses `$empty` as an operator it cannot compile, as it does `$null`.
21+
22+
New optional API: `translateFilter(where, temporalKind?, valueShape?)` in `@objectstack/driver-mongodb` takes a declared-value-shape resolver (type `ValueShapeResolver`), and `buildAggregationPipeline` a `valueShape` option; `RemoteTransport.setDeclaredValueShapeResolver` in `@objectstack/driver-turso`, which `TursoDriver` wires. `@objectstack/spec`'s shared `FILTER_LOGIC_CASES` table gains seven `$empty` cases: a backend that runs it answers `$empty` or goes red, and its harness must declare the fixture's columns.
23+
24+
`$empty` stays staged: it is not in `FILTER_OPERATORS`, so the engine's front door still refuses it until the flip card adds it, and the view operators `is_empty` / `is_not_empty` still lower to `$null`.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
"@objectstack/rest": patch
3+
---
4+
5+
**The layered view, `GET /api/v1/meta/:type/:name/layers` and the deprecated `?layers=true` flag, now answers a name with nothing behind it with the plain read's `404 RESOURCE_NOT_FOUND`, the answer it already gave a member for an unpublished app.** Before this release, a name with no layer behind it answered `200` with `code`, `overlay` and `effective` all `null`. A member asking for an unpublished app got `404`, so the difference between the two answers told the member which unpublished apps exist. ADR-0045 §3 declares a hidden app externally unobservable on every surface, and the plain read already kept that promise. This follows triage's grade on #20507.
6+
7+
Clause-②: no
8+
9+
- **What changed:** `createMetaLayeredAnswer`, the one chain both transports call after the store read (`RestServer` and the runtime dispatcher's `/meta` domain), answers a layered read with no layer present as the name's absence, before the per-caller gate runs. Each transport writes that absence in its own envelope, the one it already uses for an unpublished app: `RestServer`'s nested `{ error: { code: "RESOURCE_NOT_FOUND", message } }`, and the dispatcher's `404` error envelope. The flag's `Deprecation` and `Link` headers still ride that answer.
10+
- **Who it applies to:** every caller. The plain read answers an absent name `404` whoever asks, and so does the layered view now. A builder (`studio.access` or `setup.access`) is still served an unpublished app on both spellings. A `?package=` scope that leaves no layer behind the name is that name's absence too.
11+
- **Unchanged:** a name with any layer behind it is judged and served exactly as before. An item whose code layer is scoped away by `?package=` but whose overlay row answers is still served, with `code: null`.
12+
13+
A client that read `/layers` for a name that has never been published, and took a `200` with every layer `null` as "not saved yet", now receives `404`. Treat that `404` as the same answer. Studio's metadata client already maps a `404` from this route to every layer `null`, so the designer's "open an item that exists only as a draft" path is unchanged.
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
fix(rest): the environment-scoped `?layers=true` answer's successor `Link` names the path the request used, not the route template (#20508)
6+
7+
Clause-②: no
8+
9+
On `RestServer`'s environment-scoped mount (`api.enableProjectScoping`),
10+
`GET /api/v1/environments/env_1/meta/view/lead_all?layers=true` answered its
11+
`Deprecation` header with a successor `Link` naming
12+
`/api/v1/environments/:environmentId/meta/view/lead_all/layers`: the route
13+
template, with a literal `:environmentId` in it. A client that followed the
14+
header requested that path. The `Link` now names
15+
`/api/v1/environments/env_1/meta/view/lead_all/layers`.
16+
17+
`RestServer` builds the `Link` from the request's own path, read the way the
18+
runtime dispatcher reads its request URL, so both transports name the successor
19+
the same way. The unscoped mount's `Link` is unchanged for every name that needs
20+
no percent-encoding. A percent-encoded name now stays encoded in the `Link`
21+
(`lead%20all`, where the header used to carry a raw space), because the path is
22+
parsed as a URL path instead of being assembled from decoded route parameters.
23+
A request that carries no path of its own is still answered `Deprecation: true`,
24+
and names no successor. The body, the status and the `Deprecation` header are
25+
unchanged on both mounts.

0 commit comments

Comments
 (0)