Skip to content

Commit c662265

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-20620-migrate-meta-pairing
2 parents dab37c2 + 9b0de7d commit c662265

45 files changed

Lines changed: 1842 additions & 182 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.

‎.changeset/20802-nested-relation-filter-served.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,4 +25,4 @@ Measured through `POST /api/v1/data/:object/query` on SQLite and PostgreSQL 16 (
2525
| `{ $not: { owner: { region: "NA" } } }` | `INVALID_FILTER` / 400 | `d2`, `d4` |
2626
| `{ owner: { region: "APAC" } }` (no owner matches) | `INVALID_FILTER` / 400 | no rows |
2727

28-
On the in-memory driver, a multi-valued relation's `$contains` still matches a stored id by substring per element, so there an id that is a substring of another stored id (`u1` inside `u10`) also matches; SQLite and PostgreSQL match the element.
28+
SQLite, PostgreSQL and the in-memory driver match the element of a multi-valued relation, so an id that is a substring of another stored id (`u1` inside `u10`) does not match it.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
"@objectstack/driver-memory": minor
3+
---
4+
5+
fix(driver-memory): `$contains` / `$notContains` on a multi-valued or JSON-stored field answer by membership, as the SQL drivers do
6+
7+
Clause-②: yes (widening) — one new public method on the exported `InMemoryDriver` class, `filterContainsTest`; its return type `MemoryContainsTest` is not re-exported from the package entry. No accepted filter key or operator is added: `$contains` and `$notContains` keep their declared shape.
8+
9+
On a field whose declaration makes it JSON-stored (`multiple: true` on a `lookup`, `user`, `select`, `radio`, `file` or `image` field, a `multiselect`, `checkboxes` or `tags` field, or a structured type such as `json`), the in-memory driver now answers `{ field: { $contains: v } }` by whole-element membership: some element of the stored array equals `v`. It used to match each element by substring, so `u1` matched a row storing `['u10']` and `'red'` matched a row storing `['redwood']`. A number member answered nothing: `{ nums: { $contains: '1' } }` missed `[1, 2]`. `$notContains` is the exact complement, and a row with no value still satisfies it. A scalar text column keeps the case-exact substring test.
10+
11+
`driver-sql` gives the same answer on SQLite, PostgreSQL and MySQL; the two drivers were measured over the same fixture. The answer holds on every face of this driver:
12+
13+
- `find()` and `count()`, in both filter spellings;
14+
- the nested-relation filter on a multi-valued relation, which the engine lowers to one `$contains` per related id;
15+
- `MemoryAnalyticsService`'s query, and its SQL echo, which now renders SQLite's `json_each` membership construct for such a column.
16+
17+
The comparand is still a string. A number or boolean member is named by its text: `'1'` matches the stored number `1` (and `'1.50'` the number `1.5`), `'true'` matches the boolean `true`, and `'null'` matches a `null` member. A field the driver holds no declaration for, such as a field on an object never passed through `syncSchema`, keeps the substring reading.
18+
19+
New: `InMemoryDriver.filterContainsTest(object, field, value)` returns the one test every face above lowers `$contains` to. It is a narrow seam for the analytics face, beside `filterSubstringPattern` and `filterComparandStorageForm`. The added public method is why this entry is `minor`.
20+
21+
**If your tests relied on the old answer:** on the in-memory driver, a filter that matched an id by prefix or a tag by substring now returns only the member rows. That is what SQL already returned in production. Write `$contains` with the whole member value.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/metadata-protocol': patch
3+
---
4+
5+
fix(metadata-protocol): the by-name read of a flow name a managed package ships serves the package's flow, as the flow list does (#20946)
6+
7+
Clause-②: no
8+
9+
`flow` is in ADR-0126's Regime C: a managed package's flow is sealed, and there is no overlay read path for it. The flow list, `GET /api/v1/meta/flow`, and the execution view the automation engine binds flows from already serve the package's flow for a name a managed package ships (#20913). The by-name read, `GET /api/v1/meta/flow/:name`, did not: for such a name it served a stored flow of that name, marked as the package's flow. So the two read doors answered two different flows for one name.
10+
11+
The by-name read now applies the same rule the list applies, through the same checks. For a name a managed package ships, it serves the package's flow, with or without a package scope, whatever the stored flow's own package binding or markings say.
12+
13+
The stored flow is not deleted, rewritten or refused. It stays in the store, and the automation engine still reports it as a shadowed definition at startup. Pending drafts, flow names no managed package ships, organization-scoped rows and every other metadata type are read as before.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
fix(rest): the `hint` on a NOT NULL refusal leads with the remedy for a column that requires a value, and names schema drift only as a condition, so an author who declared `storage: { notNull: true }` is no longer sent to `os migrate` (#20963)
6+
7+
Clause-②: no
8+
9+
A field that declares `storage: { notNull: true }` without `required` is a column
10+
the database keeps NOT NULL on purpose (ADR-0113). A write with no value for it,
11+
through `POST /api/v1/data/:object` or `PATCH /api/v1/data/:object/:id`, is
12+
refused by the database and answers `400 VALIDATION_FAILED` with a `required`
13+
finding for the field. That answer was right. Its `hint` said the field is
14+
optional in the metadata and "the physical schema has drifted from metadata",
15+
and told the caller to run `os migrate`, which changes nothing for a column
16+
declared NOT NULL.
17+
18+
The `hint` now reads: "The database column for 'FIELD' requires a value: provide
19+
it, or declare the field `required` in the object metadata. If the object
20+
declares neither `required` nor `storage: { notNull: true }` for 'FIELD', the
21+
physical schema has drifted from metadata instead: run 'os migrate' to reconcile
22+
(or reset the dev database)." The first sentence holds for every NOT NULL
23+
refusal. The second names the drift case under the condition that makes it drift.
24+
25+
**What is not affected.** The status, `code`, `error`, `fields` and `object` of
26+
the answer are unchanged, and no key is added. The import row is untouched: it
27+
does not carry a `hint`. Nothing reads the field map to tell the two cases
28+
apart, so the `hint` stays advice for the author to read against the object's
29+
declaration.

‎content/docs/releases/v17/17-3.mdx‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -646,6 +646,9 @@ reached by that run.
646646
**nine** of the items on this page were live and unaddressed on that stack. Its
647647
scope is the protocol chain; everything else on this list is outside it. (It
648648
also prints `runtime 17.0.0` on a 17.3.0 install.)
649+
**Correction (2026-10-01):** from 17.4.0 the parenthetical on the `Chain:`
650+
line reads `(this runtime implements protocol 17)`; `(runtime 17.0.0)` was
651+
the 17.3.0 output. See #15585 (the human-readable line landed in #16058).
649652

650653
**Metadata and build — run `os validate` before you ship**
651654

‎examples/app-crm/tsconfig.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525
// BUILD ARTIFACT — so `tsc --noEmit` would be checking against whatever was
2626
// last built rather than against the source in this checkout, and a stale
2727
// `dist` typechecks green over an engine contract that has since moved.
28-
// `pnpm check:type-source-resolution` is the gate; it wants the `paths`
29-
// rule, not a registry entry.
28+
// `check:type-source-resolution` was the gate, and it wanted the `paths`
29+
// rule, not a registry entry, until it was retired on 2026-09-18 (#18373);
30+
// no gate checks this rule now.
3031
"paths": {
3132
"@objectstack/formula": ["../../packages/formula/src/index.ts"]
3233
}

‎examples/app-multi-package/tsconfig.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,9 @@
3838
// direction is SILENT: a `dist` merely BEHIND the source type-checks GREEN
3939
// against old declarations. This app is the fixture for a brand-new spec
4040
// surface (ADR-0130 D4's `packages[]`), which makes that exactly the
41-
// reading it must not be able to give. `pnpm check:type-source-resolution`
42-
// is the gate; it wants the `paths` rule, not a registry entry.
41+
// reading it must not be able to give. `check:type-source-resolution` was
42+
// the gate, and it wanted the `paths` rule, not a registry entry, until it
43+
// was retired on 2026-09-18 (#18373); no gate checks this rule now.
4344
//
4445
// Two keys, and the star follows a separator: a tsconfig `paths` key
4546
// without a star is an EXACT match, and the `@objectstack/spec*` spelling

‎examples/app-showcase/tsconfig.json‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,9 @@
2525
// BUILD ARTIFACT — so `tsc --noEmit` would be checking against whatever was
2626
// last built rather than against the source in this checkout, and a stale
2727
// `dist` typechecks green over an engine contract that has since moved.
28-
// `pnpm check:type-source-resolution` is the gate; it wants the `paths`
29-
// rule, not a registry entry.
28+
// `check:type-source-resolution` was the gate, and it wanted the `paths`
29+
// rule, not a registry entry, until it was retired on 2026-09-18 (#18373);
30+
// no gate checks this rule now.
3031
//
3132
// Same rule, same reason, for the email plugin: `test/email-template-locale.test.ts`
3233
// resolves this app's declared email templates through plugin-email's real

‎packages/cli/bin/run-dev.js‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -245,8 +245,9 @@ const CLI_PACKAGE_JSON = fileURLToPath(new URL('../package.json', import.meta.ur
245245
* `compilerOptions.paths` to EVERY specifier it resolves — including this
246246
* CLI's own. Ten in-tree directories carry such a rule, written for tsc so a
247247
* `typecheck` grades against a producer's source rather than its last build
248-
* (`check:type-source-resolution` requires them), and #11094 named the runtime
249-
* half "a latent runtime redirect for any tsx-honouring tool". Run this shim
248+
* (`check:type-source-resolution` required them until it was retired on
249+
* 2026-09-18, #18373), and #11094 named the runtime half "a latent runtime
250+
* redirect for any tsx-honouring tool". Run this shim
250251
* from one of them and the CLI's imports are re-routed:
251252
*
252253
* cd examples/app-multi-package

‎packages/cli/src/commands/child-env-source-loader.pin.test.ts‎

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,13 @@
3030
* likely to be re-litigated: `plugin-email` already ships the explicit
3131
* `./email-plugin.js` extension and fails identically to `formula`'s
3232
* extensionless `./registry`. Adding extensions to the redirected packages
33-
* cannot fix this, and neither can removing the `paths` blocks — those are
34-
* mandated by `pnpm check:type-source-resolution`, which was green throughout.
35-
* So was `pnpm check:test-source-alias`. The two gates cover the **types** axis
36-
* and the **vitest** axis; the axis that broke — a CLI child's **runtime**
37-
* module resolution — is covered by neither, which is why the defect lived on
38-
* `main` while every gate reported success. The general third-axis guard is
33+
* cannot fix this, and neither could removing the `paths` blocks — those were
34+
* mandated by `pnpm check:type-source-resolution` (retired on 2026-09-18 —
35+
* #18373), which was green throughout. So was
36+
* `pnpm check:test-source-alias`. The two gates covered the **types** axis and
37+
* the **vitest** axis; the axis that broke — a CLI child's **runtime** module
38+
* resolution — was covered by neither, which is why the defect lived on `main`
39+
* while every gate reported success. The general third-axis guard is
3940
* separate scope and tracked separately; this pin is the narrow half that
4041
* belongs to the fix.
4142
*
@@ -285,8 +286,8 @@ describe('no CLI command puts a child process under oclif\'s TypeScript source l
285286
+ 'packages to their .ts source there — so the child resolves a CommonJS package to '
286287
+ 'TypeScript and Node\'s CJS resolver then fails on that file\'s sibling imports '
287288
+ '(`Cannot find module \'./registry\'`), killing `os dev` before the server starts. '
288-
+ 'Neither check:type-source-resolution (types axis) nor check:test-source-alias '
289-
+ '(vitest axis) sees this; they were both green while it was broken. Pass the child '
289+
+ 'Neither check:type-source-resolution (types axis, retired 2026-09-18) nor check:test-source-alias '
290+
+ '(vitest axis) saw this; they were both green while it was broken. Pass the child '
290291
+ '`process.env` unmodified, or a NODE_ENV that is not '
291292
+ `${[...SOURCE_LOADER_ACTIVATING].map((v) => `'${v}'`).join(' or ')}`
292293
+ ' — and if a child genuinely needs dev semantics, let the child command set them '

0 commit comments

Comments
 (0)