Skip to content

Commit 945a43f

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-11042-executable-plural-meta-calls
# Conflicts: # packages/spec/liveness/object.json
2 parents 3fc0044 + 8f10a79 commit 945a43f

379 files changed

Lines changed: 11475 additions & 3687 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: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
---
2+
"@objectstack/client": minor
3+
"@objectstack/cli": minor
4+
---
5+
6+
feat(client,cli)!: `client.projects.*` becomes `client.environments.*`, the scoped sub-client becomes `ScopedEnvironmentClient`, and the unwrap keys follow the wire (#12866, #12882, ADR-0006 D2)
7+
8+
<!-- adr-0087: not-required (runtime-interface-only packages/client/src/index.ts#ObjectStackClient) The renamed surface is a member of a published runtime TypeScript class and the response-key shapes it declares inline. There is no Zod schema, no `packages/spec` declaration, no authorable key and no stored representation behind either half — measured 2026-08-28: zero `projects` envelope contracts anywhere in `packages/spec/src`, positive control being that `environments` hits do exist there. So `objectstack migrate meta` has nothing to visit and there is no tombstone to mint. The channel that reaches every affected consumer is the COMPILER, at the call site, which is strictly more precise than a ledger line; the wire half is carried by the paired control-plane release in the same coordinated window. MEASURED CAVEAT, recorded here rather than worked around: this claim is REFUSED by check-adr-0087-registration at step 4, because `packages/spec/src/api/contract.zod.ts` names `ObjectStackClient` in a JSDoc PROSE comment (line 164, describing what `unwrapResponse` keys on) while neither declaring nor importing it, and the predicate does not strip comments before scanning a metadata surface for references. Steps 1-3 pass. The disposition is left stated rather than swapped for `no-migration-prescription`, which would mechanically pass only through a detector blind spot while contradicting the migration table below it — the exact anti-pattern this gate's own header records as #8299. -->
9+
10+
11+
**BREAKING** public-API rename on `@objectstack/client`, and a breaking change to
12+
the `--format json` payload of the `os environments` command family. It lands
13+
after the v17.0.0 cut, so the lockstep launch-window convention ships it as
14+
`minor` (`scripts/check-changeset-no-major.mjs`); the version number is not the
15+
migration signal here, this entry is.
16+
17+
This is the **SDK half** of one coordinated cross-repo rename. The **producer
18+
half** is the cloud control plane, which renames the same field keys on the same
19+
endpoints. Neither half ships alone: shipping the SDK half by itself is
20+
ADR-0006 D3, permanently declined, as is any mapping layer between the two
21+
spellings.
22+
23+
## Migration
24+
25+
**No aliases exist.** The old namespace is gone, not deprecated — there is no
26+
`client.projects` getter, no `res.project ?? res.environment` hedge, and none is
27+
coming (ADR-0006 D3 declined a mapping layer with reasons; the v5.0 rename rule
28+
「no aliases」 is the standing one). Every call site moves in one edit.
29+
30+
### Method namespace
31+
32+
| before | after |
33+
| --- | --- |
34+
| `client.projects.list(…)` | `client.environments.list(…)` |
35+
| `client.projects.get(id)` | `client.environments.get(id)` |
36+
| `client.projects.create(req)` | `client.environments.create(req)` |
37+
| `client.projects.update(id, patch)` | `client.environments.update(id, patch)` |
38+
| `client.projects.delete(id, opts)` | `client.environments.delete(id, opts)` |
39+
| `client.projects.activate(id)` | `client.environments.activate(id)` |
40+
| `client.projects.rotateCredential(…)` | `client.environments.rotateCredential(…)` |
41+
| `client.projects.updateHostname(…)` | `client.environments.updateHostname(…)` |
42+
| `client.projects.updateVisibility(…)` | `client.environments.updateVisibility(…)` |
43+
| `client.projects.listRevisions(…)` | `client.environments.listRevisions(…)` |
44+
| `client.projects.listBranches(id)` | `client.environments.listBranches(id)` |
45+
| `client.projects.renameBranch(…)` | `client.environments.renameBranch(…)` |
46+
| `client.projects.deleteBranch(…)` | `client.environments.deleteBranch(…)` |
47+
| `client.projects.retryProvisioning(id)` | `client.environments.retryProvisioning(id)` |
48+
| `client.projects.listDrivers()` | `client.environments.listDrivers()` |
49+
| `client.projects.packages.*` | `client.environments.packages.*` |
50+
51+
The URL paths are unchanged — they were already on the `environments` spelling
52+
(`/api/v1/cloud/environments/…`). Only the method namespace and the response
53+
field keys move.
54+
55+
### Response keys
56+
57+
| before | after | where |
58+
| --- | --- | --- |
59+
| `res.projects` | `res.environments` | `list` (the `total` key is unchanged) |
60+
| `res.project` | `res.environment` | `get`, `update`, `activate`, `updateHostname`, `updateVisibility`, `retryProvisioning` |
61+
62+
The joined blocks on `get` (`database`, `credential`, `membership`,
63+
`organization`) keep their names, as do every `packages.*` key, the
64+
`delete`/`listBranches`/`renameBranch`/`deleteBranch` payloads (already
65+
`environmentId`-keyed), and `listRevisions`.
66+
67+
### Two declarations that were false before this change
68+
69+
Measured 2026-08-28 against the cloud repo's `main`, and corrected here rather
70+
than carried forward under a new spelling:
71+
72+
- **`create` never answered a `project` key at all.** `POST /api/v1/cloud/environments`
73+
has always answered `{ environment, warnings, durationMs, hostnameAssignment? }`.
74+
The old `{ project: any; database: any }` declaration was not merely
75+
pre-rename, it was wrong against the running control plane — and
76+
`os environments create` read `res.project.id` through it, so the default
77+
`--activate` silently never activated and the table output printed
78+
`undefined`. Both are fixed by this rename.
79+
- **`create` declares no `database` key.** That route does not send one; the key
80+
was declared NON-optional, so `res.database.driver` typechecked and threw. The
81+
method that really answers a `database` block is `get`, which keeps it.
82+
83+
The keys `create` does send beside `environment` (`warnings`, `durationMs`,
84+
`hostnameAssignment`) are deliberately still undeclared — adding them is new
85+
published surface and a separate decision.
86+
87+
### The environment-scoped sub-client (#12882)
88+
89+
The fourth `project`-spelled surface on the same class, folded in by the same
90+
maintainer ruling. ADR-0006's D1 census named three surfaces and missed this one;
91+
it was an oversight, not a deliberate retention.
92+
93+
| before | after |
94+
| --- | --- |
95+
| `client.project(id)` | `client.environment(id)` |
96+
| `ScopedProjectClient` (exported class) | `ScopedEnvironmentClient` |
97+
98+
Same no-alias rule: neither old spelling survives. `client.project(id)` is not a
99+
deprecated method, it is gone, and the exported class is gone under its old name
100+
— a `import { ScopedProjectClient }` fails at the import line, which is the
101+
loudest and most precise channel this change has.
102+
103+
Nothing about the behaviour moves: the scoped client still prefixes
104+
`/api/v1/environments/:environmentId/...`, still exposes the same `data` / `meta`
105+
/ `batch` / `packages` shape, and the thrown guard message becomes
106+
`[ObjectStack] environment(id): environmentId is required`.
107+
108+
**Deliberately NOT renamed, because each is a different surface needing its own
109+
decision:** `setProjectId` / `getProjectId` on the client — `getProjectId` is a
110+
cross-package protocol contract that `packages/runtime` and
111+
`packages/metadata-protocol` both speak, so it is a coordinated rename, not a
112+
local one — and the REST API config keys `enableProjectScoping` /
113+
`projectResolution`, which are live keys read by `packages/cli/src/commands/serve.ts`.
114+
The docblocks that name them are worded so they stay true.
115+
116+
Note for whoever compiles the release notes: four other pending changesets in
117+
this release describe methods on `ScopedProjectClient` under its old name
118+
(`client-unannotated-return-erasure`, `client-saveitem-ifmatch-header`,
119+
`client-meta-saveitem-query-options`, `client-precise-sdk-return-types`). They
120+
were accurate when written and are deliberately left alone; this entry is the one
121+
that renames the class.
122+
123+
### JSDoc
124+
125+
The `create` docblock claimed the server delegates to
126+
`ProjectProvisioningService.provisionProject`. That spelling has zero hits in the
127+
control plane (measured 2026-08-28). Both this SDK's docblock and
128+
`os environments create`'s now name the **endpoint** instead, which is the one
129+
identifier an in-repo reader can verify — the class lives in a repo this one
130+
never compiles against, so no gate here could ever have caught the rot.
131+
132+
## CLI
133+
134+
`os environments list | show | create | switch | bind` follow the same rename.
135+
No flag, argument, exit code or command id changes. `--format json` / `--format yaml`
136+
payloads are `formatOutput(res, …)` straight from the control-plane response, so
137+
their top-level keys change with the wire: a script reading `.projects` or
138+
`.project` from those payloads reads `.environments` / `.environment` instead.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@objectstack/metadata": patch
3+
"@objectstack/runtime": patch
4+
---
5+
6+
fix(metadata,runtime): retire the `policies` dead pointer in both artifact registrars, and pin the map that carried it (#12894)
7+
8+
Zero behaviour change, by construction. Both readers of an artifact boot carried
9+
a `policies` -> `policy` entry — the artifact door's `ARTIFACT_FIELD_TO_TYPE`
10+
(`packages/metadata/src/plugin.ts`) and `AppPlugin`'s ADR-0057 `SECURITY_FIELDS`
11+
list (`packages/runtime/src/app-plugin.ts`) — and **neither could ever match**.
12+
`ObjectStackDefinitionSchema` is a `strictObject` that declares no top-level
13+
`policies` key, so a definition carrying a `policies` array is refused outright
14+
by the door's strict parse and reaches neither registry. The word is real but
15+
lives one level down: on a permission set `policies` is an alias for
16+
`rowLevelSecurity` (`PERMISSION_SET_KEY_ALIASES`) — a key on an **item**, never a
17+
collection. Both entries are removed, each leaving in place the note the map
18+
already writes for a retirement: what it pointed at, and why it could not match.
19+
20+
That was the third entry retired from `ARTIFACT_FIELD_TO_TYPE` for exactly this
21+
reason (`themes`, then `roles` -> `positions`, which "matched nothing and
22+
silently dropped compiled positions"). So the deletion ships with the thing the
23+
two predecessors did not have — a check that fails when the pattern recurs:
24+
25+
- `check:stack-collection-maps` now reconciles **eight** hand-maintained
26+
enumerations against the schema, not seven. `SECURITY_FIELDS` is the new
27+
eighth, and how it was missing is the finding rather than a footnote: it is
28+
the only one of the eight that pairs its keys as `[collection, kind]` tuples,
29+
which neither existing extractor could read, so the site was skipped rather
30+
than reported. Re-adding `policies` — or any other key the schema does not
31+
declare — to **either** registrar now fails the gate with the site named.
32+
- A new `tupleFirstItems` extractor reads that shape, with a self-test case
33+
(13 assertions, up from 12) covering the comment/nesting cases the flat
34+
string-array extractor already pins.
35+
36+
The mirror-image half of the same measurement is **carried, not shipped**:
37+
`capabilities` is a declared top-level collection that `SECURITY_FIELDS`
38+
registers and the door's map does not, making `AppPlugin` its sole registrar on
39+
an artifact boot. Adding it to the door changes what an artifact boot registers,
40+
so it is measured and handed to the route-ownership decision (#12892) instead of
41+
being smuggled in here. The new waiver row records the asymmetry in place.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/driver-sql': patch
4+
'@objectstack/driver-memory': patch
5+
'@objectstack/driver-mongodb': patch
6+
'@objectstack/objectql': patch
7+
---
8+
9+
`min`/`max` over a **boolean** aggregand now answer the numbers `0`/`1` on every face — maintainer ruling 2026-08-28 (#11152, option A), superseding #11249's `false`/`true`: booleans aggregate as numbers, with no per-aggregate exception, so one flag column's `sum`/`avg`/`min`/`max` all answer in one numeric domain.
10+
11+
FROM → TO, per face: `driver-sql` (every dialect, `driver-sqlite-wasm` included via the shared compiler) no longer re-presents `min`/`max` results over a declared boolean as JSON booleans — `false`/`true``0`/`1`; row reads (`find()`) still present booleans, and `min`/`max` over an empty window still answer `null`. `driver-memory` (data and analytics faces) and objectql's in-memory fallback compare booleans as the numbers they are worth — `false`/`true``0`/`1`; strings, dates and numbers reach the same comparison they always did. `driver-mongodb` wraps `$min`/`$max` in the same boolean-only `$cond` coercion `$sum`/`$avg` use — `false`/`true``0`/`1`; null/missing still pass through, so the empty window still answers `null`. A caller reading `min`/`max` over a boolean column as a JSON boolean should read the number (`0` is false-y, `1` truthy, so boolean coercion at the call site keeps working).
12+
13+
The cross-driver aggregation conformance fixture (`AGGREGATION_ROWS`, `@objectstack/spec/data`) now carries the boolean column those rulings are pinned by: `flag` (3 true / 3 false), with cases for `sum`=3, `avg`=0.5, `min`=0, `max`=1, `count`=6, `count_distinct`=2 and a grouped `min` over the deliberately asymmetric groups — the reach gap #11065 and #11151 were both found through (a boolean aggregand no conformance cell could see) is closed.
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
---
2+
'@objectstack/spec': minor
3+
'@objectstack/service-messaging': patch
4+
---
5+
6+
`IDataEngine` declares the optional `syncObjectSchema?(objectName: string): Promise<void>` member (#12482) — on-demand single-object physical schema sync: create/alter the object's table, or for a federated (external) object register its DDL-free read metadata (ADR-0015 §18). Additive contract catch-up under the 2026-08-25 #11833 ruling's item-4 precedent as executed by #12248: the member #12010's inventory left "not verified" is verified — implemented on `ObjectQL`, consumed cross-package by two service packages, both until now through consumer-local structural recovery (`service-datasource`'s `ConnectionEngineLike.syncObjectSchema?`, called per bound external object after its driver connects; `service-messaging`'s system-table provisioning via an `as unknown as` cast whose own comment recorded the member "lives on the concrete ObjectQL engine, not the contract"). FROM undeclared (consumers cast or re-declare structurally) TO declared-optional on `IDataEngine` (consumers read `engine.syncObjectSchema` directly and keep their runtime probes). `service-messaging` drops the now-redundant cast (behaviour unchanged). Optional, so existing `IDataEngine` implementers and test doubles are unaffected. No runtime change.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
Close the entry-nameability invariant on the four remaining public entries that leaked it (#12414). Generalizing #11350/#11709's `defineStack`-only probe to every public entry found the class was never fully closed: seven (entry, type) pairs across four entries still had a `define*` factory's return type expand to mention a type the entry itself did not re-export — `Book`, `FormField`, `NavigationItem` from the root `@objectstack/spec` entry, `UnknownAuthoringKeyFinding` from `@objectstack/spec/kernel`, `FilterCondition` and `StateNodeConfig` from `@objectstack/spec/ai`, and `FilterCondition` from `@objectstack/spec/ui`. Each is repaired the same way as the prior two rounds: one re-export line on the leaking entry, from the module that already declares the type as public on its own subpath. A consumer writing an un-annotated `export const x = defineBook(...)` (or the `/ui`, `/ai`, `/kernel` equivalent) previously hit TS2883 naming an unaddressable hash-named internal dist chunk and could not build; all seven now declaration-emit cleanly with no annotation required. No runtime change. `packages/spec/entry-nameability.baseline.json`, the shrink-only ledger `check:entry-nameability` (#11986) reads, is now empty — the class it was tracking is closed.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
---
2+
"@objectstack/metadata-core": patch
3+
"@objectstack/metadata": patch
4+
---
5+
6+
fix(metadata-core,metadata): split the form-view predicate root vocabulary per surface, so a field-level `current_user` test is not false-flagged (#12915)
7+
8+
Same-day correction to the unbound-root boot notice. The notice judged **every**
9+
form-view predicate against one vocabulary (`record` / `previous` / `parent` /
10+
`data`), sourced faithfully from the contract prose — which, for the field-level
11+
slot, was stale.
12+
13+
`current_user` and its ADR-0068 alias roots (`user`, `ctx.user`, `os.user`)
14+
**resolve on a field-level `visibleWhen`** since objectui#6010; three spec text
15+
sites still said otherwise until #12930 re-measured them, and one of those sites
16+
was the sentence this policy was written against. A legacy artifact carrying a
17+
legitimate `current_user.role == "admin"` field predicate was therefore reported
18+
as faulting open — the cry-wolf failure the notice is explicitly built to avoid,
19+
and the one that trains operators to ignore the channel.
20+
21+
The vocabulary is now per surface, which is what the contract actually says:
22+
23+
- **Field-level** (`BOUND_FORM_FIELD_PREDICATE_ROOTS`): the shared base plus
24+
`current_user`, `user`, `ctx`, `os`. Silent on all of them.
25+
- **Section-level** (`BOUND_FORM_VIEW_PREDICATE_ROOTS`, unchanged in name and
26+
value): the base alone. `current_user` is still flagged there — the section
27+
docblock states it is unbound at that level and faults open.
28+
29+
Two limits of the field binding deliberately do **not** change the answer: it is
30+
a rendering rule rather than authorization (an authoring hazard, not a
31+
version-drift one), and the scope is empty on the console's public `/f/:slug`
32+
route (equally true of a freshly built current artifact, so it says nothing
33+
about the artifact's era — the only thing this notice claims to detect).
34+
35+
The emitted warn line now prints the bound roots **per surface, and only for the
36+
surfaces the findings implicate**, so an operator is never shown a rule their
37+
artifact has no instance of. Findings carry a `surface` field.
38+
39+
`unboundRootsInCelSource` takes the vocabulary as an optional second argument;
40+
its default is unchanged (the stricter base), so existing callers behave exactly
41+
as before.

0 commit comments

Comments
 (0)