Commit a1b61e0
* fix(spec,rest,runtime): request bodies are checked against the schemas the catalog declares (#3899)
The API catalog (plugin-rest-api.zod.ts) declared requestSchema on 16
routes while almost no mounted entry point validated its body — a
malformed request did not 400, it executed different semantics (an
unfiltered full read on /query, markRead(userId, []) on a misnamed key,
a flow registered under the key `undefined`, a one-letter-off toggle
body ENABLING the flow it meant to disable).
Wire the declared schemas at the real entry points, make the catalog
stop promising what nothing performs, and gate both directions:
- rest: POST /data/:object/query, POST /data/:object,
PATCH /data/:object/:id, POST /data/:object/batch and
POST /data/:object/createMany now safeParse the declared contract and
answer 400 VALIDATION_FAILED + fields[] (the #3933/#3944 pattern);
the query route also pins the PATH object into the forwarded query.
- runtime: POST /notifications/read validates
MarkNotificationsReadRequestSchema; the automation registerFlow /
updateFlow / toggle bodies get strict hand-written guards (keys.ts
pattern); the analytics-entry validationFailure helper is hoisted to
validation-failure.ts and shared.
- spec: the catalog drops the four ghost notification endpoints
(#3612 removed the routes), fixes the automation trigger path to
/trigger/:name and drops its never-true request schema, adds the
POST /:object/query entry, repoints create/update at the schemas the
routes actually validate, and drops requestSchema from bodyless
GET/DELETE entries.
- gates: schema-name references must resolve to real exports and sit on
body-carrying methods (spec); every declared requestSchema on a
mounted route has a violating-body -> 400 conformance case with a
completeness ratchet (rest + runtime request-schema-gate suites).
Suites: spec 7132, rest 536, runtime 977 all green; check:generated 8/8.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EynH7cngDczRGMkGMudJpC
* fix(spec): QuerySchema declares the search contract ADR-0061 actually serves
The first CI run of the #3899 request-schema gate rejected the dogfood
search proof's own wire shape: `{ search: 'retail', searchFields:
['industry'] }` answered 400. The schema was the wrong half — QuerySchema
declared only the structured FullTextSearchSchema form while ADR-0061 D1
("the client sends only the query text"), the engine executor
(search-filter.ts), and the search-conformance ledger all serve the bare
string plus the validated `searchFields` narrowing.
- `search` becomes `string | FullTextSearch` (string is the canonical
Tier-1 spelling; the object form keeps the declared Tier-2 knobs)
- `searchFields` is formally declared (the ADR's own P1 item), noted as
server-intersected — can only narrow, never widen
- rest request-schema gate pins the ADR-0061 wire shape as a positive
case so entry validation can never 400 it again
- regenerated: references docs + authorable-surface (adds
`data/Query:searchFields`); all 8 artifact gates green
spec suite 7145 green; both previously-failing dogfood files pass
locally (showcase-search 4/4, two-factor-lockout 5/5 — the 2FA pair does
not reproduce here and is green on main @ 5d21a48's identical job).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EynH7cngDczRGMkGMudJpC
* docs(changeset): record the QuerySchema search-contract widening
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EynH7cngDczRGMkGMudJpC
* chore(spec): classify query/search (now a union leaf) and query/searchFields in the liveness ledger
The search-contract repair turned `search` into a union the liveness
walker does not descend, and declared `searchFields` — both landed
UNCLASSIFIED on the freshly-seeded query ledger (#4286). `search`
becomes a leaf entry (the object form's experimental flags stay audited
by their own describe markers inside FullTextSearchSchema);
`searchFields` gets its live entry pointing at the resolveSearchFields
intersection and the ADR-0061 dogfood proof.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EynH7cngDczRGMkGMudJpC
* fix(metadata-protocol): QUERY_AST_KEYS names searchFields, as its type demands
`QUERY_AST_KEYS` is typed `Record<keyof QueryAST, true>` precisely so a key
added to the spec is a compile error here rather than silent drift at the
REST boundary — and it did its job: declaring `searchFields` on QuerySchema
turned the whole build red until the key was listed.
The hand-maintained `'searchFields'` entry in RESERVED_LIST_QUERY_PARAMS goes
with it. It was there as a "transport-only extra the AST does not name"; the
AST names it now, so it arrives through the `QUERY_AST_KEYS` spread and the
type-level pin covers it. Keeping both would have been the second source that
list exists to avoid.
No behavior change on either path: the GET normalizer already accepted
`?searchFields` / `$searchFields` and the engine already read `ast.searchFields`
(ADR-0061). metadata-protocol 122, objectql 1373, full build 71/71 green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EynH7cngDczRGMkGMudJpC
* docs(query): the search contract's canonical spelling appears in the hand-written docs
Both hand-written query pages taught only the structured `search: {query,
fields}` form — `query-syntax.mdx` even typed it `search?: FullTextSearch`,
which is now literally wrong. The bare string is the canonical ADR-0061
spelling (D1: the client says what to search for, the server decides which
fields), it is what every surface sends, and it is what the dogfood proof
asserts; the top-level `searchFields` narrowing was undocumented entirely.
Same gap this PR exists to close, one layer up: a reader following these
pages learned half the contract. Both forms are now shown, with the
structured one named as equivalent for the two members that drive the
expansion.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EynH7cngDczRGMkGMudJpC
* test(driver-mongodb): a hung MongoDB binary download skips the suite instead of stopping Test Core
These three suites start a real mongod via mongodb-memory-server, which
fetches a ~123 MB binary from fastdl.mongodb.org at module load. Each already
anticipated that fetch FAILING — try/catch, warn, skip — precisely so a
blocked download costs a skipped suite rather than the monorepo test job.
A catch cannot express the other half. When the fetch HANGS, the top-level
await never settles: the suite neither runs nor skips, it stops. Observed
twice on this branch (Test Core attempts 1 and 2 of run 30610017310), both
frozen at the identical point with zero output from this package and no skip
warning, both force-killed 10 minutes later by the #4250 stall guard with
`@objectstack/driver-mongodb#test` as the surviving task. `instance
.launchTimeout` does not cover it — that bounds spawning mongod once the
binary is on disk, a later phase than the fetch.
`createTestMongod` puts a 120s deadline on the wait so a hang lands in the
skip branch the suites already had. Not a workaround for this PR's diff: the
same task on main 40 minutes earlier logged `Downloading MongoDB "8.2.6": 0%
… 100%` and passed on a cache miss, so this is the network changing under an
unbounded wait. Any PR touching @objectstack/spec invalidates the cache and
runs straight into it.
Verified: 160/160 still pass locally on the success path (binary cached), and
a mocked never-settling create() resolves to undefined at the deadline rather
than hanging. The helper stays out of dist — tsup entry is src/index.ts only,
which does not reference it (mongodb-memory-server is a devDependency).
Refs #4250.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EynH7cngDczRGMkGMudJpC
* fix(driver-mongodb): declare Node's globals — the test helper is typechecked source
`src/test-mongod.ts` uses setTimeout / clearTimeout / console and turned the
package's `tsc --noEmit` red. The `.test.ts` files that call it have always
used the same globals, but tsconfig excludes `**/*.test.ts`, so this package
never needed `types: ["node"]` until a shared helper became typechecked
source — and it cannot be named `*.test.ts`, or vitest would collect it as a
suite with no tests.
Same one-line declaration a dozen sibling packages already carry (client,
core, lint, cli, metadata-protocol, …); @types/node is already a devDep here.
I missed this locally by verifying with build + eslint + vitest: tsup's dts
step only covers the `src/index.ts` entry graph, which this helper is
deliberately outside of, so nothing I ran typechecked it. Re-verified with the
command CI actually uses — `turbo run build --filter='./packages/*'`, 57/57 —
plus `tsc --noEmit` in this package and its 160 tests.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EynH7cngDczRGMkGMudJpC
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent 411116e commit a1b61e0
28 files changed
Lines changed: 1200 additions & 183 deletions
File tree
- .changeset
- content/docs
- data-modeling
- protocol/objectql
- references
- api
- data
- packages
- metadata-protocol/src
- plugins/driver-mongodb
- src
- rest/src
- runtime/src
- domains
- spec
- liveness
- src
- api
- data
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
432 | 432 | | |
433 | 433 | | |
434 | 434 | | |
| 435 | + | |
| 436 | + | |
| 437 | + | |
| 438 | + | |
| 439 | + | |
| 440 | + | |
| 441 | + | |
| 442 | + | |
| 443 | + | |
| 444 | + | |
| 445 | + | |
| 446 | + | |
| 447 | + | |
| 448 | + | |
| 449 | + | |
435 | 450 | | |
436 | 451 | | |
437 | 452 | | |
| |||
444 | 459 | | |
445 | 460 | | |
446 | 461 | | |
447 | | - | |
448 | | - | |
| 462 | + | |
| 463 | + | |
449 | 464 | | |
450 | 465 | | |
451 | 466 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
64 | 64 | | |
65 | 65 | | |
66 | 66 | | |
67 | | - | |
| 67 | + | |
| 68 | + | |
68 | 69 | | |
69 | 70 | | |
70 | 71 | | |
| |||
751 | 752 | | |
752 | 753 | | |
753 | 754 | | |
| 755 | + | |
| 756 | + | |
| 757 | + | |
| 758 | + | |
| 759 | + | |
754 | 760 | | |
| 761 | + | |
755 | 762 | | |
756 | 763 | | |
757 | | - | |
758 | | - | |
759 | | - | |
760 | | - | |
| 764 | + | |
| 765 | + | |
| 766 | + | |
| 767 | + | |
| 768 | + | |
| 769 | + | |
| 770 | + | |
| 771 | + | |
| 772 | + | |
761 | 773 | | |
762 | 774 | | |
763 | 775 | | |
764 | 776 | | |
765 | | - | |
766 | | - | |
767 | | - | |
768 | | - | |
769 | | - | |
770 | | - | |
771 | | - | |
772 | | - | |
| 777 | + | |
| 778 | + | |
| 779 | + | |
| 780 | + | |
| 781 | + | |
| 782 | + | |
| 783 | + | |
| 784 | + | |
773 | 785 | | |
774 | 786 | | |
775 | 787 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
149 | 149 | | |
150 | 150 | | |
151 | 151 | | |
152 | | - | |
| 152 | + | |
| 153 | + | |
153 | 154 | | |
154 | 155 | | |
155 | 156 | | |
| |||
161 | 162 | | |
162 | 163 | | |
163 | 164 | | |
164 | | - | |
| 165 | + | |
165 | 166 | | |
166 | 167 | | |
167 | 168 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
529 | 529 | | |
530 | 530 | | |
531 | 531 | | |
532 | | - | |
| 532 | + | |
533 | 533 | | |
534 | 534 | | |
535 | 535 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
548 | 548 | | |
549 | 549 | | |
550 | 550 | | |
551 | | - | |
| 551 | + | |
552 | 552 | | |
553 | 553 | | |
554 | 554 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
52 | 52 | | |
53 | 53 | | |
54 | 54 | | |
55 | | - | |
| 55 | + | |
56 | 56 | | |
57 | 57 | | |
58 | 58 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
126 | 126 | | |
127 | 127 | | |
128 | 128 | | |
129 | | - | |
| 129 | + | |
| 130 | + | |
130 | 131 | | |
131 | 132 | | |
132 | 133 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
840 | 840 | | |
841 | 841 | | |
842 | 842 | | |
843 | | - | |
844 | | - | |
845 | | - | |
846 | | - | |
| 843 | + | |
| 844 | + | |
| 845 | + | |
| 846 | + | |
847 | 847 | | |
848 | 848 | | |
849 | 849 | | |
| |||
882 | 882 | | |
883 | 883 | | |
884 | 884 | | |
885 | | - | |
| 885 | + | |
| 886 | + | |
| 887 | + | |
| 888 | + | |
886 | 889 | | |
887 | 890 | | |
888 | 891 | | |
| |||
Lines changed: 3 additions & 10 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
23 | | - | |
| 23 | + | |
24 | 24 | | |
| 25 | + | |
25 | 26 | | |
26 | | - | |
27 | | - | |
28 | | - | |
29 | | - | |
30 | | - | |
31 | | - | |
32 | | - | |
33 | | - | |
34 | | - | |
| 27 | + | |
35 | 28 | | |
36 | 29 | | |
37 | 30 | | |
| |||
0 commit comments