Skip to content

Commit 7d29573

Browse files
authored
Merge branch 'main' into claude/issue-18926-near-miss-census-population
2 parents a1baba0 + d4cb05c commit 7d29573

55 files changed

Lines changed: 2035 additions & 274 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: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
`element:text.variant` accepts the nine values objectui's text node publishes — `h1``h6`, `body`, `caption`, `overline` — and still accepts `heading` and `subheading` (#17108).
6+
7+
Clause-②: yes (widening)
8+
9+
Release 1 of 2 for the objectui#7450 convergence (director batch #71, 2026-09-07, maintainer verbatim 「其他同意」), split across two releases by the maintainer's decision of 2026-09-09, option B. This release is **additive only**: the accepted set grows by seven and nothing is refused that was accepted before, so an out-of-repo author can converge on a released pin before any spelling stops working.
10+
11+
Measured on the 17.3.0 declaration, per value, through `ElementTextPropsSchema.safeParse`: `h1``h6` and `overline` were refused with `invalid_value`; they are accepted now. `heading`, `subheading`, `body` and `caption` were accepted and are accepted now. A value outside the eleven — `small` — is still refused with `invalid_value` at path `variant`, so the enum remains a closed set rather than having stopped judging `variant` at all.
12+
13+
- **`.optional().default('body')` is kept, deliberately.** An `element:text` node parsed without a `variant` still materialises `variant: 'body'`, exactly as before. Absence is the one thing a widening must not move, and the `ui:text` side of the platform deliberately does *not* synthesise `body` for an absent `variant` (objectui#6942) — that asymmetry is pre-existing and is left where it was.
14+
- **⛔ Nothing is retired.** `heading` and `subheading` become named refusals carrying migration hints in **release 2**, which is a separate card and is blocked on a value-level retirement mechanism that does not exist yet: `retiredKey()` and ADR-0087 D2 retire a *key*, not a *value*. Authors who want to move early can write `h2` for `heading` and `h3` for `subheading`; neither spelling stops working in this release.
15+
- **No renderer changes here.** `element:text`'s renderer, its designer inspector options and its i18n rows are objectui's, on the released pin, and land on objectui's side of the sequence.
16+
17+
Generated projections follow the declaration: `api-surface-declarations/ui.txt` gains the seven members on `ElementTextPropsSchema` and on `ComponentPropsMap['element:text']`, and the `content/docs/references/ui/component.mdx` property table widens. `check:api-surface` reports nothing removed or narrowed.
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/core": minor
4+
"@objectstack/objectql": minor
5+
"@objectstack/driver-sql": minor
6+
"@objectstack/driver-turso": minor
7+
---
8+
9+
feat(spec,core,objectql,driver-sql,driver-turso): a transport can declare it has no transactions, and every transaction gate reads the declaration instead of method presence (#18063)
10+
11+
Maintainer ruling, decision batch #148 item 3, letter B, 「同意」 2026-09-17, verbatim and untranslated:
12+
13+
> `packages/spec`: the driver contract gains a way for a transport to **declare 「no transactions」** (the dev picks the smallest spelling the existing capability/contract surface already has — a capability bit is preferred over a new key), and the engine's transaction gating reads the declaration instead of method presence.
14+
15+
**`DriverCapabilities` gains one live bit, `transactionsUnsupported`.** A transport sets it to say that a handle it issued would be a FALSE SUCCESS rather than a missing feature: the caller gets a handle, the writes execute and are already durable, `rollback()` resolves and undoes nothing. Absence means `false`, exactly like `batchSchemaSync`, so a driver that declares nothing keeps the behaviour it has today.
16+
17+
**⛔ This is not `DriverCapabilities.transactions` un-retired, and the difference is not cosmetic.** That key was tombstoned in 17.0.0 under ADR-0049 enforce-or-remove and STAYS tombstoned — writing it is still a compile error and still a parse refusal carrying its prescription. It claimed "I support transactions" and nothing read it; this one declares "my transport cannot honour one" and the engine dispatches on it. Reviving the name would have inverted the record's own `absence = false` convention into a tri-state, turned a documented refusal into silent acceptance of a value whose meaning had changed underneath it, and made the tombstone's published text ("no code in any repository ever read it") false. A new key costs one bit; the name costs all of that.
18+
19+
**Adding a bit to a record enforce-or-remove has pruned SATISFIES that ADR rather than reversing it.** The audit removed thirty-one bits for one stated reason — no code anywhere read them — and kept the three where method presence provably cannot carry the signal. This change is the creation of the missing reader: `driverSupportsTransactions()` (exported from `@objectstack/spec`) is the one definition of the gate, and all FOUR places that used to spell `typeof driver.beginTransaction === 'function'` ask it — `ObjectQL.transaction()`, `ScopedContext.transaction`, the `ScopedContext` begin/commit/rollback trio, and `@objectstack/core`'s `engineCanRollBack`. The bit arrives WITH its reader, in the same change, which is the honest order the ADR asks for.
20+
21+
**Why method presence could not carry it.** `TursoDriver extends SqlDriver`, whose `beginTransaction()` opens a real knex transaction, so the inherited method reported the libSQL REMOTE transport as transactional. It is not — `RemoteTransport`'s data methods take no `options` argument at all, so a handle cannot reach the statement that would have to join it. A subclass cannot opt out of a door it did not open. This is the mirror of `batchSchemaSync`, which exists because a subclass can inherit `syncSchemasBatch` from a base whose transport batches while its own cannot.
22+
23+
**What changes for a caller.** On a datasource whose driver declares the bit, `engine.transaction()` now takes the DECLARED non-transactional path (ADR-0119 D1) instead of opening a transaction it cannot honour: the degrade warns once per datasource — naming the declaration, not a missing method — and `{ require: true }` throws `TransactionUnsupportedError` before the callback writes anything. `ScopedContext.transaction` and the discrete begin/commit/rollback trio read the same predicate; the trio's `begin` returns `null`. Both are the answers a driver with no `beginTransaction` already received.
24+
25+
**`driver-turso`.** The remote face declares `transactionsUnsupported: true`; local and embedded-replica inherit `false` from the base and are untouched. `TursoDriver.beginTransaction()` publishes the inherited declaration instead of `Promise<any>` — the annotation the earlier `any` was masking an LSP violation to avoid, dissolved rather than widened: the remote arm returns `never` (it refuses), so the only arm that still returns is the base's. `SqlDriver.beginTransaction()` keeps its narrow `Promise<Knex.Transaction>`; nothing in the base was widened.
26+
27+
**`@objectstack/core`.** `engineCanRollBack()` — the ADR-0119 D4 gate that `@objectstack/metadata-protocol` uses for `batchData` / `updateManyData` / `deleteManyData` under `options.atomic`, and that `runMigrationJournal()` uses to decide whether to start at all — reads the same predicate. It has to: it does not open the transaction itself, it vouches that `engine.transaction()` will, and on a driver that declares the bit the engine now takes its non-transactional path. A gate still reading method presence would vouch for a runtime that is about to run the callback with no transaction, so the atomic batch would answer `rollback` over writes that stayed on disk and the journal would write `chunk_done` rows its own contract says mean "committed". What a caller sees on such a datasource instead: `batchData({ atomic: true })` refuses with `501 NOT_IMPLEMENTED` — retry without `atomic`, or probe `capabilities.transactionalBatch` on `/discovery` first — and `runMigrationJournal()` refuses with `MigrationJournalRefusal('NOT_IMPLEMENTED')` before writing a single journal row. Both are the answers a driver with no `beginTransaction` already received.
28+
29+
**`RemoteTransport` loses `beginTransaction()`, `commit()` and `rollback()`.** They are a published surface, and this is **minor** rather than major on the ruling's own stated ground: that transport never honoured a transaction, so no working behaviour is withdrawn. They had already become unreachable from every caller in the repository when the driver started refusing them; they are now gone, and the declaration keeps them gone by design rather than by audit.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
`latencyMs` and `frequencyHours` name their unit in the published describe, and `check:duration-unit-keys` refuses the agreement shape
6+
7+
`AIUsageRecord.latencyMs` carried no `.describe()` at all, and
8+
`DatabaseLevelIsolationStrategy.backup.frequencyHours` described `'Backup
9+
frequency'`. Both keys already carried their unit in the key NAME and in a JSDoc
10+
block above it — and neither of those is a channel the published JSON Schema or
11+
`content/docs/references/**` prints. So the reference page published
12+
`frequencyHours | integer | Backup frequency` and left the reader to infer the
13+
unit from the key name, which on a duration is a guess with a 3600x error on the
14+
other side of it. Both describes now name the unit, and the `description` in the
15+
shipped JSON Schema moves with them.
16+
17+
**Ruled 2026-09-18 (decision batch #158 item 5, letter A).** The AGREEMENT shape
18+
— a unit in the key name, the SAME unit in the JSDoc, none in the describe — IS
19+
an offence. `check:duration-unit-keys` carried a carve-out
20+
(`!jsdocUnits.some((u) => keyUnits.includes(u))`) that spared it for one release
21+
while the question sat open, together with two self-test cases pinned as
22+
DEFERRED and a header note recording shape (b) as repealed. The carve-out is
23+
gone, those two cases are POSITIVE controls, and shape (b) is a base refusal
24+
again. Agreement between a key name and a source comment is agreement between
25+
two channels the published page does not print; it says nothing about the one
26+
it does.
27+
28+
⚠️ **This also makes an already-published sentence true.** The changeset for
29+
#15939 states that the gate refuses a key whose JSDoc names a unit its describe
30+
does not, *"or there is no describe at all"* — which over-claimed by exactly the
31+
two rows above while the carve-out stood. The two rows are remediated and the
32+
carve-out is removed, so the claim now holds of the gate; nothing is edited in
33+
place to make it hold.
34+
35+
The `EpochMs` instant exemption reads the JSDoc channel too, riding the same
36+
ruling. It refused a describe that contradicted the schema but never a JSDoc
37+
that did, while the duration-type exemption beside it refused all three
38+
channels — the same lie with two answers depending on which exemption class the
39+
key fell into. No row in the tree carried the shape; a fixture pair pins it.
40+
41+
⛔ No published key, accept set, default or runtime behaviour moves. The two
42+
changes to shipped artefacts are `description` strings.
43+
44+
Clause-②: no

.changeset/18318-evalcontext-no-query-api.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
The member's docblock said it was "implemented opportunistically by call sites that have a query engine", and no call site ever could: `ctx.api` was read **zero** times in this package — control in the same sweep, `ctx.user`, three reads in `stdlib.ts` — so the three functions reached no evaluation scope however completely a caller populated the member. An author who wrote a predicate to the declaration got `runtime: found no matching overload for 'dyn.lookup(string, dyn)'` instead, and because an unevaluable predicate refuses the write it guards, a validation rule authored that way locked **every** write on its object. The harm came from the declaration existing, not from the implementation missing, so it is removed rather than implemented — with the reason written at the deletion site, and with no shim, alias or reserved spelling left behind.
1010

11-
**Migration — `api: { … }` → delete the property.** There is no replacement key and nothing to re-point: every implementation ever passed there was discarded before evaluation, so removing the property changes no result your predicates produce. TypeScript is where you will hear about it: an `EvalContext` literal carrying `api` now fails to compile, which is the whole of the break. Reading a related record's field from inside a predicate remains unexpressible in any spelling — that capability is tracked as its own card, relationship traversal (`record.crm_account.type`), and deliberately not as `os.lookup` queries; no schedule is implied by this removal.
11+
**Your fix — delete the `api: { … }` property.** There is no replacement key and nothing to re-point: every implementation ever passed there was discarded before evaluation, so removing the property changes no result your predicates produce. TypeScript is where you will hear about it: an `EvalContext` literal carrying `api` now fails to compile, which is the whole of the break. Reading a related record's field from inside a predicate remains unexpressible in any spelling — that capability is tracked as its own card, relationship traversal (`record.crm_account.type`), and deliberately not as `os.lookup` queries; no schedule is implied by this removal.
1212

1313
<!-- adr-0087: not-required (runtime-interface-only packages/formula/src/types.ts#EvalContext) `EvalContext` is a published runtime TypeScript interface with no metadata surface — no Zod schema, no `packages/spec` declaration, no stored representation — so `objectstack migrate meta` has nothing to rewrite and the ledger has no channel to carry. The compiler at the consumer's own call site reaches every affected caller instead. Nothing is re-pointed either: `buildScope()` never read `ctx.api`, so the whole remedy is deleting a property that was already inert. -->
1414

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`$orderby` is declared twice — `ODataQuerySchema.$orderby` and `QueryTransportParamsSchema.$orderby` now cross-reference each other, and a pin holds the two accept sets apart (#18977).
6+
7+
Clause-②: no. No accept set moves and no export is added, removed or renamed: the change is two docblocks in published source (`src/api/odata.zod.ts`, `src/data/data-engine.zod.ts`) plus a new pin test. Measured — `check:generated` reports all 16 generated artifacts up to date, `check:api-surface` and `check:authorable-surface` included.
8+
9+
The two declarations are **complementary refusals**: each accepts exactly what the other rejects, and neither pointed at the other, so reading one of them carefully and completely still produced the wrong answer about the other.
10+
11+
| `$orderby` value | `ODataQuerySchema` | `QueryTransportParamsSchema` (`DataEngineSortSchema`) |
12+
|:---|:---|:---|
13+
| `'name desc'` / `'-created_at'` | accepted | REFUSED |
14+
| `['name desc', 'email asc']` | accepted | REFUSED |
15+
| `[{field, order}]` | REFUSED | accepted |
16+
| `{name: 'asc'}` / `{name: 1}` | REFUSED | accepted |
17+
18+
- **Which one grades a query bag**: `QueryTransportParamsSchema`, reached from `FindDataRequestSchema.query` through `QueryWithTransportSchema` — the schema `POST /data/:object/query` parses its body against. `ODataQuerySchema` grades no runtime door: measured on this tree, its only consumers are the `OData.buildUrl` helper in its own file and its own unit test.
19+
- **The refusal on the transport side is deliberate and stays**`#18704` settled it: lowering an OData sort *expression* means PARSING, and a second parser beside the door's is how one rule gets two implementations that disagree. Widening either side to close the gap is a decision, not a tidy-up, so this change closes the **reader's** half only.
20+
- **The string forms are not unserved.** `normalizeSortNodes` (`@objectstack/metadata-protocol`) reads `'name desc'`, `'-created_at'` and the `string[]` form at the shared ingress behind `GET /data/:object`, the export route and in-process `findData`. A querystring spelled the OData way works; the same bag sent as a `POST /data/:object/query` body answers `400 VALIDATION_FAILED`. The difference is the door, and neither door is `ODataQuerySchema`.
21+
- **The cost this repairs was already paid.** objectui#9554 was filed, triaged, graded and dispatched against a shipped `object-grid` producer that had been sending the canonical shape all along, because the filing seat read the OData declaration and quoted it correctly.
22+
23+
`src/api/odata-orderby-dual-declaration.test.ts` is the mechanical half: 25 cases pinning each side's accept set, their disjointness (with the lit control that neither set is empty), and which of the two `FindDataRequestSchema.query` is graded by. Widening or narrowing either declaration turns it red and lands the author on the cross-reference.
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): the connector header no longer teaches `retryConfig` as the remedy for a rate-limited upstream (#18983)
6+
7+
`packages/spec/src/integration/connector.zod.ts` ships inside this package —
8+
`files[]` carries `src/**/*.zod.ts`, and the file is present in the published
9+
tarball — so its header TSDoc is text consumers read, and the generated
10+
reference page is rendered from it. That header ended its "no outbound rate
11+
limiting" paragraph with "what L3 does declare for a rate-limited upstream is
12+
`retryConfig` — whose `retryableStatusCodes` default `[408, 429, 500, 502, 503,
13+
504]` includes `429` — and `health.circuitBreaker`", which reads as a remedy.
14+
15+
It is not one. `packages/spec/liveness/connector.json` records all eight
16+
`retryConfig` sub-keys and every `health.circuitBreaker` sub-key as `dead`
17+
(verifiedAt 2026-09-17), and outside `packages/spec` nothing reads either: no
18+
retry loop consumes the strategy, the backoff, the jitter or that status-code
19+
list, so the `429` in it never causes a retry, and no breaker ever opens. An
20+
author who followed that sentence wrote configuration that parses, stores, and
21+
is then silently ignored.
22+
23+
The sentence now carries the wording PR #18979 landed for the same claim in
24+
`packages/spec/docs/SYNC_ARCHITECTURE.md`: both keys are **declared but
25+
currently unimplemented**, with a pointer to the liveness ledger, and they are
26+
explicitly neither retired — both are still declared and still parse, so an
27+
author writing them sees no error — nor left to the host, since
28+
`ConnectorProviderContext` carries exactly `name`, `label`, `description`,
29+
`icon`, `type`, `providerConfig`, `auth` and `loadPackageFile`, and a provider
30+
factory is therefore never handed either key.
31+
32+
**Prose only — zero behaviour change.** No schema, declaration, default or
33+
accept set moves, and the keys' fate stays ADR-0049's to rule on rather than
34+
being prejudged here. The generated reference page
35+
`content/docs/references/integration/connector.mdx` follows from `gen:docs`; it
36+
is not published by any package in this workspace.

.claude/settings.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,9 @@
4747
"Bash(curl -sS -X GET https://api.github.com/repos/objectstack-ai/objectstack/commits/*/check-runs* *)",
4848
"Bash(curl -sS -X GET https://api.github.com/repos/objectstack-ai/objectui/commits/*/check-runs* *)",
4949
"Bash(curl -sS -X PATCH https://api.github.com/repos/objectstack-ai/objectstack/pulls/* *)",
50-
"Bash(curl -sS -X PATCH https://api.github.com/repos/objectstack-ai/objectui/pulls/* *)"
50+
"Bash(curl -sS -X PATCH https://api.github.com/repos/objectstack-ai/objectui/pulls/* *)",
51+
"Bash(curl -sS -X POST https://api.github.com/repos/objectstack-ai/objectstack/pulls/*/ccr/ready_for_review *)",
52+
"Bash(curl -sS -X PUT https://api.github.com/repos/objectstack-ai/objectstack/pulls/*/ccr/auto_merge *)"
5153
],
5254
"deny": [
5355
"mcp__github__issue_write",

0 commit comments

Comments
 (0)