Skip to content

Commit 36ec14e

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/rest-read-path-field-degradation-4240d5
2 parents b8ac03a + 05154a1 commit 36ec14e

129 files changed

Lines changed: 1568 additions & 2182 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/adapter-hono-auth-wildcard-yields.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
---
2-
"@objectstack/adapter-hono": patch
2+
"@objectstack/hono": patch
33
---
44

55
fix(adapters/hono): the auth wildcard yields paths the auth service does not own (#4117)
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
docs(adr-0104): amend the upgrade-path advertisement clause to record how it landed (#4253/#4284) — `os migrate meta` names the migrations without gate status, because gate status is per deployment (2026-07-27 addendum) while `meta` acts on source, which deploys to zero-or-N of them; status is reported where a datastore is actually open. Releases nothing.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': patch
3+
'@objectstack/runtime': patch
4+
---
5+
6+
Discovery stops telling Cloud/Enterprise deployments that nothing implements `ai` (#4093 follow-up).
7+
8+
`CORE_SERVICE_PROVIDER` recorded `null` for `ai` because no **workspace** package provides it, and `serviceUnavailableMessage('ai')` therefore produced *"No implementation ships for the 'ai' slot"*. That is false: `@objectstack/service-ai` registers the slot in `objectstack-ai/cloud`. The table conflated "not in this repository" with "does not exist" — the same class of wrong answer the table was introduced to end, one step further out.
9+
10+
Verified against the cloud repository rather than inferred: `packages/service-ai/src/plugin.ts` calls `ctx.registerService('ai', …)`, and the package is `private: true`, so there is genuinely nothing to install — which is why `null` stays right and an `Install X` sentence would still be wrong. `search`, `workflow` and `graphql` were checked the same way and nothing registers them in either repository, so their `null` and their "nothing ships" sentence are accurate.
11+
12+
`ai` now carries a `REMEDY_DETAIL` sentence — *"Provided by @objectstack/service-ai in ObjectStack Cloud/Enterprise — no implementation ships in the open framework"* — the mechanism `ui` already used. Both discovery (`services.ai.message`) and the `/ai` 501 body report it.
13+
14+
This **removes** code: `/ai`'s domain had a local message override, added because the shared sentence was wrong there. Correcting the table fixed the domain *and* discovery, which the override could never reach, so the override and `capabilityUnavailable`'s `message?` parameter are both gone. A slot whose sentence is wrong needs the table corrected, not a local exception.
15+
16+
Also corrected: three places still describing `/ai`'s absent-service answer as a 404 (`docs/api/client-sdk.mdx`, `docs/releases/v17.mdx`, and a comment in `packages/client`'s URL-conformance test) — stale since that answer became 501.
17+
18+
FROM → TO: `services.ai.message` and the `/ai/*` 501 body change text. Nothing branches on either — `status` and `enabled` are the contract, the message is prose for humans and agents.
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/plugin-approvals": patch
4+
---
5+
6+
fix(spec,plugin-approvals): the two approval vocabularies are derived, not hand-matched (#3786)
7+
8+
`sys_approval_request.status` and `sys_approval_action.action` spelled their
9+
option lists out — five values and twelve — each under a "Keep in sync with
10+
`ApprovalStatus` / `ApprovalActionKind` (spec/contracts)" comment, while the
11+
contract held the same sets as bare type unions. Seventeen strings matched by
12+
hand across a package boundary, with nothing checking them. They did all still
13+
agree; the sweep that found them (#3786) verified that verbatim before changing
14+
anything.
15+
16+
Agreeing is not the same as being held, and both directions of drift are quiet:
17+
18+
- a value the **column** accepts and the contract omits is invisible to every
19+
consumer typed against the contract — the row exists and nothing can narrow it;
20+
- a value the **contract** declares and the column rejects surfaces only at write
21+
time, on whichever tenant first reaches that transition.
22+
23+
An audit vocabulary is a bad place for either. So the contract now publishes the
24+
lists as values — `APPROVAL_STATUSES` and `APPROVAL_ACTION_KINDS` — with
25+
`ApprovalStatus` / `ApprovalActionKind` derived from them via
26+
`(typeof X)[number]`, and the two columns spread the constants. The per-entry
27+
rationale (which action kinds move the flow, which are thread-only, why
28+
`returned` differs from `recalled`) moved onto the constants, where the values
29+
live.
30+
31+
**New exports, no behaviour change.** The emitted option lists are byte-identical
32+
— verified against the built artifact before and after. Existing imports of the
33+
two types are unaffected; the types resolve to the same unions.
34+
35+
`approval-vocabularies.test.ts` pins the qualifier that derivation alone cannot:
36+
the columns agree with the contract *while the spread is there*, and the test
37+
fails if either is re-inlined as a literal that has drifted. It also guards the
38+
guard (an unresolvable import would compare two empty lists and pass) and asserts
39+
the two vocabularies stay distinct, since a copy-paste pointing one column at the
40+
other constant would satisfy "derived from the contract" while being the wrong
41+
vocabulary entirely.
42+
43+
Verified by mutation in both directions: adding a value to `APPROVAL_STATUSES`
44+
propagates into the built `sys_approval_request.status` options (the derivation
45+
is live, not a stale build), and re-inlining a drifted literal fails
46+
`sys_approval_request.status offers exactly the contract statuses, in order`.
Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
---
2+
"@objectstack/account": patch
3+
"@objectstack/cli": patch
4+
"@objectstack/client": patch
5+
"@objectstack/client-react": patch
6+
"@objectstack/cloud-connection": patch
7+
"@objectstack/connector-mcp": patch
8+
"@objectstack/connector-openapi": patch
9+
"@objectstack/connector-rest": patch
10+
"@objectstack/connector-slack": patch
11+
"@objectstack/console": patch
12+
"@objectstack/core": patch
13+
"@objectstack/driver-memory": patch
14+
"@objectstack/driver-mongodb": patch
15+
"@objectstack/driver-sql": patch
16+
"@objectstack/driver-sqlite-wasm": patch
17+
"@objectstack/embedder-openai": patch
18+
"@objectstack/formula": patch
19+
"@objectstack/hono": patch
20+
"@objectstack/knowledge-memory": patch
21+
"@objectstack/knowledge-ragflow": patch
22+
"@objectstack/lint": patch
23+
"@objectstack/mcp": patch
24+
"@objectstack/metadata": patch
25+
"@objectstack/metadata-core": patch
26+
"@objectstack/metadata-fs": patch
27+
"@objectstack/metadata-protocol": patch
28+
"@objectstack/objectql": patch
29+
"@objectstack/observability": patch
30+
"@objectstack/platform-objects": patch
31+
"@objectstack/plugin-approvals": patch
32+
"@objectstack/plugin-audit": patch
33+
"@objectstack/plugin-auth": patch
34+
"@objectstack/plugin-dev": patch
35+
"@objectstack/plugin-email": patch
36+
"@objectstack/plugin-hono-server": patch
37+
"@objectstack/plugin-pinyin-search": patch
38+
"@objectstack/plugin-reports": patch
39+
"@objectstack/plugin-security": patch
40+
"@objectstack/plugin-sharing": patch
41+
"@objectstack/plugin-webhooks": patch
42+
"@objectstack/rest": patch
43+
"@objectstack/runtime": patch
44+
"@objectstack/sdui-parser": patch
45+
"@objectstack/service-analytics": patch
46+
"@objectstack/service-automation": patch
47+
"@objectstack/service-cache": patch
48+
"@objectstack/service-cluster": patch
49+
"@objectstack/service-cluster-redis": patch
50+
"@objectstack/service-datasource": patch
51+
"@objectstack/service-i18n": patch
52+
"@objectstack/service-job": patch
53+
"@objectstack/service-knowledge": patch
54+
"@objectstack/service-messaging": patch
55+
"@objectstack/service-package": patch
56+
"@objectstack/service-queue": patch
57+
"@objectstack/service-realtime": patch
58+
"@objectstack/service-settings": patch
59+
"@objectstack/service-sms": patch
60+
"@objectstack/service-storage": patch
61+
"@objectstack/setup": patch
62+
"@objectstack/studio": patch
63+
"@objectstack/trigger-api": patch
64+
"@objectstack/trigger-record-change": patch
65+
"@objectstack/trigger-schedule": patch
66+
"@objectstack/types": patch
67+
"@objectstack/verify": patch
68+
"create-objectstack": patch
69+
"objectstack-vscode": patch
70+
---
71+
72+
chore(packaging): CHANGELOG.md ships in every npm tarball (#4261)
73+
74+
The AGENTS.md post-task checklist requires breaking changesets to carry their
75+
FROM → TO migration because "this text ships to consumers as `CHANGELOG.md`
76+
inside the npm package and is what an upgrading agent greps after the tombstone
77+
error." That delivery path was severed for 68 of the 69 publishable packages:
78+
npm packs `package.json` / `README*` / `LICENSE*` unconditionally but — unlike
79+
older npm versions — not `CHANGELOG.md`, and the canonical
80+
`"files": ["dist", "README.md"]` whitelist never named it. Measured on npm
81+
10.9.7: `npm pack --dry-run` on `@objectstack/types` shipped 3 files while its
82+
70KB `CHANGELOG.md` stayed behind. Only `@objectstack/spec` listed it
83+
explicitly.
84+
85+
The tombstone-error scenario is precisely the one where the repo is out of
86+
reach — the upgrading agent has `node_modules` and nothing else — so the
87+
migration text has to ride in the tarball. Every publishable package now
88+
declares `CHANGELOG.md` in `files`, and the canonical whitelist is
89+
`["dist", "README.md", "CHANGELOG.md"]`.
90+
91+
The other half is the gate: `check:published-files` gains a fifth invariant,
92+
COMPLETE — a whitelist that fails to cover `CHANGELOG.md` fails the
93+
always-required lint job, so the next package cannot silently sever the path
94+
again. `@objectstack/spec`'s per-package EXTRA_ENTRIES exemption dissolves
95+
into the canonical set.
96+
97+
Consumer-visible change: one more file per install (the package's changelog,
98+
e.g. 70.8KB for `@objectstack/types`), and `grep -r "removed key"
99+
node_modules/@objectstack/*/CHANGELOG.md` now finds the migration it was
100+
promised.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
---
3+
4+
docs: fix the plugin-spec CHANGELOG example that still taught the retired
5+
`onUpgrade` hook as running automatically (#4212 family). Releases nothing.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
Repo-plumbing only: the pending changeset `adapter-hono-auth-wildcard-yields` referenced the Hono adapter by its old `@objectstack/adapter-hono` name, which `changeset version` rejects because the workspace package is named `@objectstack/hono`. Renamed the entry; a full sweep of every other pending changeset (and `pre.json`) against the workspace found nothing else stale. Releases nothing by itself.
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
---
2+
"@objectstack/spec": major
3+
"@objectstack/plugin-security": patch
4+
---
5+
6+
refactor(data)!: the QueryAST request surface stops declaring what no executor runs — `joins` and `windowFunctions` removed, six search flags and `aggregations[].filter` marked experimental, and the liveness ledger now governs the query surface (#4286)
7+
8+
#4196 removed one declared-but-inert member from `FieldNode`. Applying the same
9+
method to the rest of the request surface (#4286) found 12 more members of
10+
`QueryAST` that no executor runs — `packages/objectql`'s `engine.ts` contains
11+
zero reads of any of them on the query path. This change dispositions the
12+
mechanical tiers and closes the gate that let the class stay invisible.
13+
14+
**Removed (tombstoned): `query.joins` and `query.windowFunctions`.**
15+
16+
- `joins` — no engine or driver ever read it; a query carrying it silently ran
17+
as a single-table query. Related-record retrieval already has a live
18+
spelling: `expand`. The orphaned `JoinNode` / `JoinNodeInput` /
19+
`JoinNodeSchema` / `JoinType` / `JoinStrategy` exports are deleted with the
20+
key (`data/JoinNode`, `data/JoinType`, `data/JoinStrategy` leave the
21+
published JSON schemas).
22+
- `windowFunctions``find()` never applied it, so every OVER clause it
23+
declared was silently dropped. The one live door is the SQL driver's own
24+
`findWithWindowFunctions(object, query)` (driver-level, not on the
25+
`IDataDriver` contract), and its input is a flat driver shape the spec
26+
vocabulary never matched — `WindowFunctionNodeSchema` declared `field` /
27+
`over` / `frame` members that door never read. The `WindowFunction` /
28+
`WindowSpec` / `WindowFunctionNode` exports are deleted with the key.
29+
30+
**FROM → TO**
31+
32+
| Was | Now |
33+
| :--- | :--- |
34+
| `joins: [{ type: 'inner', object: 'customer', on: … }]` | `expand: { customer_id: { object: 'customer', fields: ['name'] } }` |
35+
| `joins` for one related column | `fields: ['customer_id.name']` (dotted path) |
36+
| `windowFunctions: [{ function: 'rank', … }]` in a query | `aggregations` + `groupBy`, or rankings in report/dashboard metadata |
37+
| OVER-clause SQL from an embedder | `sqlDriver.findWithWindowFunctions(object, { windowFunctions: [{ function, alias, partitionBy?, orderBy? }] })` |
38+
39+
The one-line fix: **delete the key**. Both are `retiredKey()` tombstones on the
40+
non-strict `BaseQuerySchema`, so authoring either fails `tsc` (input type
41+
`never`) and a query still carrying one — even as an empty array — fails to
42+
parse with the prescription itself. `QueryAST` is a request shape, never stored
43+
in stack metadata, so there is no `os migrate meta` step: the removals are
44+
registered as protocol-18 **semantic** migrations (`query-joins-retired`,
45+
`query-window-functions-retired`), the #4196 precedent.
46+
47+
Compat note for the REST boundary: both names remain **reserved** list-query
48+
parameters while the tombstones live (`retiredKey()` keeps a key in
49+
`keyof QueryAST`, which feeds `RESERVED_LIST_QUERY_PARAMS`), so nothing changes
50+
for objects with fields named `joins`/`windowFunctions` — the un-reservation
51+
happens when the tombstones age out, and is called out in
52+
`metadata-protocol`'s `QUERY_AST_KEYS` comment for whoever does it.
53+
54+
**Marked `[EXPERIMENTAL — not enforced]` (no wire or compat impact):**
55+
`search.fuzzy` / `operator` / `boost` / `minScore` / `language` / `highlight`
56+
(the ADR-0061 expansion reads only `query` + `fields`) and
57+
`AggregationNode.filter` (a SQL `FILTER (WHERE …)` affordance neither the SQL
58+
builders nor the in-memory fallback applies). Authoring one is now a
59+
declaration, not a silent no-op.
60+
61+
**Deliberately NOT dispositioned here** (they want a maintainer call, #4286
62+
steps 3–4): `having` (the strongest enforce candidate — `engine.aggregate()`
63+
currently rebuilds the driver AST without it), and `cursor` / `distinct`
64+
(shipped SDK producers `QueryBuilder.cursor()` / `.distinct()`; `distinct` is
65+
mis-wired — its only observable effect is suppressing the REST list count).
66+
All three are recorded `dead` with evidence in the new ledger.
67+
68+
**The gate:** `QuerySchema` joins the liveness ledger through the gate's
69+
`SPEC_ONLY_SCHEMAS` override (the `webhook` precedent) as governed type
70+
`query` — the first governance of what *callers* write into a query rather
71+
than what authors write into metadata files. `packages/spec/liveness/query.json`
72+
classifies all 27 walked members (15 live with evidence, 7 experimental via
73+
describe markers, 5 dead), so the next declared-but-inert request member fails
74+
CI instead of needing a person to notice it.
75+
76+
`@objectstack/plugin-security` (patch): the FLS predicate guard's
77+
`windowFunctions` walk is pruned — the clause no longer exists to leak through.
78+
The `having` and `aggregations[].filter` walks stay, deliberately: those
79+
members remain declared, and the guard being ready is what makes enforcing
80+
them later safe.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
---
3+
4+
chore(spec): register the `adr0104-field-file-collection` dogfood proof tag in the ADR-0054 registry, honestly unbound — the collection contract it guards is `lifecycle` on the system `sys_file` object, already bound to `data-lifecycle`. Gate-only (`packages/spec/scripts/`, not published), so this releases nothing.
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
---
2+
'@objectstack/spec': major
3+
'@objectstack/service-automation': patch
4+
---
5+
6+
feat(spec)!: retire `waitEventConfig.timeoutMs` / `.onTimeout``wait` never had a timeout (#4158)
7+
8+
Both keys described a timeout and neither delivered one, so protocol 18 removes the pair
9+
rather than leaving a promise the runtime does not keep (PD #10).
10+
11+
- **`onTimeout`** had **zero** readers. No path ever inspected it, so neither `'fail'` nor
12+
`'continue'` ever happened — and its `.default('fail')` stamped a decision nothing made
13+
onto every wait node. The showcase set `onTimeout: 'continue'`, which did nothing.
14+
- **`timeoutMs`** said *"maximum wait time before timeout"* while its only reader used it
15+
as the timer **duration** when `timerDuration` was absent. It did something, just not
16+
what it claimed.
17+
18+
Together they declared a timeout `wait` does not have: a run resumes when its timer
19+
elapses or its signal arrives, never on a deadline. Real timeout semantics are left
20+
unimplemented deliberately — they should be built to a requirement, not retrofitted to
21+
fit two keys that happened to be declared.
22+
23+
`timeoutMs` **converts to `timerDuration`** rather than being dropped, because that is
24+
what it did. It is stringified on the way: the target is `z.string()` while `timeoutMs`
25+
was `z.number()`, and `parseIsoDuration` reads a bare numeric string as milliseconds — so
26+
`timeoutMs: 60000` and `timerDuration: '60000'` are the same wait. Moving the number
27+
unstringified would have produced a block that no longer parses, which a test pins. With
28+
`timerDuration` already set it is dropped instead: the executor's `??` never looked past
29+
the duration, so it was already dead metadata.
30+
31+
Both leave the **load path** (`retiredFromLoadPath`), which is the registry's existing
32+
split: a key retired for being *renamed* keeps a load window, because punishing an author
33+
for a spelling nobody warned them about is pointless; a key that **misdescribed itself**
34+
does not, because silently absorbing it lets the author keep believing they configured a
35+
timeout. That is why `api.requireAuth`, the tool/app/flow inert keys and RLS `priority`
36+
all left it too. The migration chain converts stored sources mechanically; the schema
37+
tombstones name the replacement.
38+
39+
One fixture interaction worth recording: the #4045 lift fixture used
40+
`waitEventConfig.timeoutMs` to demonstrate its fourth ledger entry, and the fixture
41+
harness replays the whole table — so its `after` described an end state protocol 18 makes
42+
unreachable. It now lifts `eventType` instead. The harness caught this itself.

0 commit comments

Comments
 (0)