Skip to content

Commit 09ae986

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-17080-per-release-spec-changes
2 parents 1737a24 + f347c79 commit 09ae986

81 files changed

Lines changed: 239867 additions & 3580 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: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
---
2+
'@objectstack/spec': minor
3+
---
4+
5+
`api-surface-declarations/<entry>.txt` — every export of every published entry point now ships a readable pin of the `.d.ts` declaration text the packed build actually emits for it, and the 27-entry `api-surface-signatures.json` hash it subsumes is retired (#16045).
6+
7+
`Clause-②: yes (widening)`
8+
9+
Until now this package pinned its public surface on one axis. `api-surface/<entry>.json` records each export as `name (kind)` — 5336 rows across 17 entry points, re-derived on the landing tree — and a signature change, a renamed interface field and a dropped union member move **none** of them. The only shape pin was `api-surface-signatures.json`: 27 rows, 0.5% of the surface, and reference-level even there, because it hashed `checker.typeToString()`, which prints `z.input<typeof ActionSchema>` without expanding it. A breaking shape change to a ratified public type could pass every witness green.
10+
11+
- **Text, ⛔ not a hash, deliberately.** A digest answers "did the bytes move" with one opaque bit whose known failure at scale is that a red one gets *accepted* rather than investigated. Each shard holds one block per declaration — `// ── Name (kind) ──` followed by the declaration verbatim — so a diff names the export and shows the change, and the existing review discipline is what guards it.
12+
- **The input is the packed `.d.ts` reached through the `exports` map**, i.e. the declarations a consumer installs, never `src/`. Two of the manifest's 19 `exports` entries are asset subpaths with no declaration (`./openapi.json`, `./package.json`), which is why this artifact and `api-surface/` both hold 17 shards.
13+
- **What it costs, measured on the landing tree**: 12,661,943 bytes (12.08 MiB) of text across 17 shards, 237,706 lines, 1.02 MiB gzipped against this package's ~17.6 MiB compressed `dist`. The skew is extreme — the median declaration is 81 bytes and the 20 largest hold ~65% of the bytes, because a Zod schema's packed declaration is its fully expanded structural type. That expansion is exactly what makes an inner field rename visible; it also means four declarations exceed 20,000 lines each.
14+
- **Leading TSDoc is excluded**, so a re-worded `.describe()` does not churn this artifact — documentation drift stays `check:docs`'s axis.
15+
- **The retirement is a strict superset, proven before it landed**: all 27 factory names resolve to a declaration block in `api-surface-declarations/root.txt`, 0 missing. For those 27 declarations text and hash discriminate the same amount (both print a type reference); what is *gained* is the 5309 other declarations, including the schemas those factories point at, whose expanded blocks are where an inner-key narrowing shows up. Nothing published read the retired file: it was not in this package's `files[]`.
16+
- **Sharded per entry point from day one**, for the reason `api-surface/` is: the merge queue rebuilds server-side where no custom merge driver runs, so two PRs sharing one generated file evict the second.
17+
18+
Regenerate with `pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec gen:api-surface-declarations`; `check:api-surface-declarations` names that command when it fails. It reads the built dist, so a missing or stale one is a hard refusal in both modes rather than a green run over nothing.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
docs(spec): the `RETIRED_KEYS_BY_MAJOR` Lifecycle docblock names both rejected states, and stops contradicting check (b3)'s printed remedy
6+
7+
`RETIRED_KEYS_BY_MAJOR`'s docblock is shipped text — it reaches consumers in `dist/index.d.ts` — and since check (b3) landed, two of its sentences were false:
8+
9+
- **「The one state the gate rejects」**. Check (b3) rejects a *second* state: a NESTED row whose def this build emits but whose dotted path it does not. That state has no aging clock behind it (a nested key never reaches `authorable-surface/` at all), so it is not the aged-out steady state the paragraph described.
10+
- **「Entries are permanent」**, against check (b3)'s own refusal text, which ends `… or delete the entry from packages/spec/src/migrations/registry.ts`. An author following the docblock would not delete; an author following the gate would — two shipped instructions in this repo pushing two people who each did as they were told in opposite directions.
11+
12+
The Lifecycle paragraph now:
13+
14+
- scopes the aging-out steady state to a **top-level** tombstone, and says why a nested row can never be in it;
15+
- lists **both** rejected states with the check that owns each and the remedy that check prints — still-LIVE (b2), nested-and-unresolvable (b3) — and states the routing rule that decides which one a row is judged by (a row is read as a path only when its `name` half carries a dot AND this build emits no top-level property of that exact name, so a live dotted top-level key such as `@odata.context` stays on (b2)'s map);
16+
- reconciles permanence with deletion instead of leaving them to contradict: a row that was ever TRUE of some build is history and is never deleted, while a row (b2) or (b3) refuses was never true of any build, so deleting it removes a false claim rather than a record;
17+
- repeats (b3)'s own ⛔ — it cannot yet tell a wrong row apart from every truthful one, and for the shapes it names the remedy is to teach the check, never to delete a row that is telling the truth.
18+
19+
The `## What reads it` bullet for check (b) and the `@see` roster gain (b3) for the same reason: it reads this table, and neither named it.
20+
21+
**No behaviour moves.** No gate, schema, export or registry entry is touched — the set of metadata that validates is byte-for-byte what it was. What changes is the text an author reads when a gate refuses their row.
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
**BREAKING (published artifact narrows)** — `packages/spec/json-schema/**` now states two of the rules it used to leave entirely to the runtime, so a validator reading the published files stops answering PASS on metadata the platform then refuses (#18670 item 2).
6+
7+
Clause-②: yes (narrowing)
8+
9+
`z.toJSONSchema()` has no arm for a `custom` check: on zod 4.4.3 a plain record, the same record with a `.refine()`, and the same record with an **aborting** `.refine()` all project byte-identically. Every rule written as a refinement was therefore enforced by the runtime and absent from the published file — the direction in which an author's, or an AI's, validator says yes right up to the moment the platform says no.
10+
11+
Two named patterns now project, and only those two:
12+
13+
- **at least one of these keys is present** — emitted as `anyOf` of one `required` per key. `shared/Expression.json` states the source-or-ast rule, so `{ "dialect": "cel" }` is refused by the published file exactly as the runtime already refused it.
14+
- **a string with at least one non-whitespace character** — emitted as `minLength: 1` plus the pattern `\S`. Every evaluated and typed expression slot states it, so a whitespace-only `source` is refused at the door.
15+
16+
**⛔ Not a behaviour change, and no document the runtime accepts becomes refused.** Both patterns are EXACT rather than approximate: a key absent from a JSON object is the only way for its value to read `undefined`, and `String.prototype.trim` removes exactly the ECMA-262 whitespace set that `\S` is the complement of. Both equalities are pinned over their whole input space in `packages/spec/scripts/refinement-projection.test.ts`, including every ECMA-262 WhiteSpace and LineTerminator code point. No refinement was weakened, removed or added; the runtime accepts and refuses exactly what it did before.
17+
18+
**The list is CLOSED.** `packages/spec/src/shared/refinement-projection.ts` declares the vocabulary and builds each predicate from its own declaration, so the rule the runtime enforces and the keywords the file publishes cannot name different things. A refinement outside that list stays unprojected and keeps its `x-dropped-refinements` annotation. Adding an arm is a public-contract decision with its own measurement, never a refactor — and ⛔ never an open-ended zod-to-JSON-Schema translator over the whole population.
19+
20+
**Proof of work, in the shrink-only ledger.** `packages/spec/dropped-refinements.baseline.json` reads 201 published schemas / 553 dropped sites, from 246 / 750: 45 rows deleted, 75 rows shrunk, 197 sites closed, zero sites added anywhere. The generator now prints the closed population per pattern on every run (137 `required-one-of`, 60 `non-blank-string`), and reports a site that projects with no declared pattern on its own line.
21+
22+
<!-- adr-0087: not-required (no-migration-prescription) Nothing an author can write is removed, renamed or re-spelled: no spec key, no export and no config field changes, and the accepted set of metadata documents is byte-for-byte what it was. What changed is a machine-readable DECLARATION catching up with the runtime it always described, so there is nothing for `objectstack migrate meta` to rewrite and no stored representation to convert. -->
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
---
2+
"@objectstack/cli": patch
3+
"@objectstack/client": patch
4+
"@objectstack/client-react": patch
5+
"@objectstack/driver-memory": patch
6+
"@objectstack/driver-mongodb": patch
7+
"@objectstack/driver-turso": patch
8+
"@objectstack/mcp": patch
9+
"@objectstack/observability": patch
10+
"@objectstack/plugin-auth": patch
11+
"@objectstack/rest": patch
12+
"@objectstack/runtime": patch
13+
"@objectstack/service-cache": patch
14+
"@objectstack/service-i18n": patch
15+
"@objectstack/service-job": patch
16+
"@objectstack/service-package": patch
17+
"@objectstack/service-queue": patch
18+
"@objectstack/service-realtime": patch
19+
"@objectstack/service-storage": patch
20+
"@objectstack/spec": patch
21+
"@objectstack/types": patch
22+
---
23+
24+
The TypeScript examples in these packages' **published** `README.md` now compile against the package they document — 43 of the 44 blocks the `measure-markdown-ts-blocks` census reported as syntactically valid and wrong, in documents that ship inside the npm tarball.
25+
26+
`README.md` is listed in every one of these packages' `files[]`, so these bytes are the artefact a consumer — or a consumer's AI — reads and copies. What the census counted was not style: the examples named options the packages no longer accept, chained a method that returns a promise, and implemented interfaces they never imported.
27+
28+
The corrections, by class:
29+
30+
- **Legacy option vocabulary.** `@objectstack/client-react`'s hooks take `fields` / `orderBy` / `limit` / `where`, not `select` / `sort` / `top` / `filters`, and `PaginatedResult` carries `records`, not `value`. `@objectstack/service-job` takes `timeoutMs`, `@objectstack/service-queue` takes `maxAttempts`, and `IDataEngine.find` takes `where`.
31+
- **Async registration used synchronously.** `ObjectKernel.use()` returns `Promise<this>`, so `kernel.use(a).use(b)` does not chain; the examples now `await` each registration. `ObjectKernelConfig` has no `plugins` member.
32+
- **Interfaces implemented but never imported.** Several plugin examples wrote `implements Plugin` with no import, which bound to the DOM's `Plugin`; they now import `Plugin` / `PluginContext` and declare the required `init`. `PluginContext.getService<T>()` has no default type argument, so the examples that read a service now name its contract.
33+
- **Removed or never-existing API.** `@objectstack/driver-memory`'s default export is a legacy `onEnable` object that `kernel.use()` refuses — the quick start now registers through `DriverPlugin`; its persistence adapters take an options bag under `persistence.adapter`. `defineStack` has no `driver` key. `@objectstack/rest`'s `RestServer` takes the host `IHttpServer` first and `registerRoutes()` takes no arguments; `RouteManager` is constructed on a server. `@objectstack/spec`'s `ObjectSchema.parse()` returns the value — the `{ success, data }` envelope is `safeParse`'s. `useMutation` has no `onMutate` / mutation context.
34+
35+
No runtime code changed and no gate was added (#18715 ruling F). One block is deliberately left: `@objectstack/knowledge-ragflow`'s README writes `source.options.datasetId`, which is what the shipped adapter reads and what `KnowledgeSourceSchema` does not declare — correcting the document either way would contradict one of the two, so the conflict is reported rather than papered over.

‎.gitattributes‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,10 +144,10 @@ packages/spec/authorable-surface.base.json merge=os-regen
144144
packages/spec/authorable-defaults/** merge=os-regen
145145
packages/spec/json-schema.manifest/** merge=os-regen
146146
packages/spec/api-surface/** merge=os-regen
147+
packages/spec/api-surface-declarations/** merge=os-regen
147148
packages/spec/src/meta-spelling/meta-url-data.generated.ts merge=os-regen
148149
packages/spec/export-origins/** merge=os-regen
149150
packages/spec/declaration-map/** merge=os-regen
150-
packages/spec/api-surface-signatures.json merge=os-regen
151151
docs/protocol-upgrade-guide.md merge=os-regen
152152
docs/audits/2026-07-unknown-key-strictness-ledger.counts.md merge=os-regen
153153
content/docs/references/** merge=os-regen

‎.github/workflows/lint.yml‎

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -4172,32 +4172,6 @@ jobs:
41724172
- name: Ref'd-timer probe containment
41734173
run: pnpm check:refd-timer-probe
41744174

4175-
# The TYPE axis of the same invariant (#8180). The gate above reads
4176-
# `vitest.config.*` and nothing else, so the identical exposure on the
4177-
# type axis was unguarded repo-wide — and its symptom is likewise a GREEN
4178-
# `typecheck`. Measured on `packages/qa/downstream-contract` (#8021):
4179-
# identical checkout, identical stale `dist`, a narrowing injected into
4180-
# `packages/spec/src` in SOURCE only — without a `paths` block
4181-
# `tsc --noEmit` exited 0 and clean; with one it reported the `TS2322` the
4182-
# frozen fixture should always have produced. The comment beside the
4183-
# type-check-debt step below documents the same mechanism as expected
4184-
# state ("tsc resolves workspace imports through each dependency's built
4185-
# `dist/*.d.ts`"), and turbo's `typecheck` dependsOn `^build` does not
4186-
# reach it for the same reason it does not reach the runtime axis.
4187-
# NOT a port of the gate above: tsc's `paths` algorithm is exact-key-wins
4188-
# then longest-prefix (Vite's is first-match-wins), type-only imports
4189-
# COUNT here, and `tsconfig.json` is JSONC that inherits through
4190-
# `extends`. The trap is also different and worse — `@objectstack/spec*`,
4191-
# star not after a separator, folds every namespace onto one target and
4192-
# type-checks GREEN against the wrong module instead of crashing.
4193-
# The registry is measured (51 of 76 packages on the day it landed),
4194-
# shrink-only and audited in BOTH directions. It finds no new offender
4195-
# today: it is a ratchet, so the remediation list is finite and cannot
4196-
# grow behind anyone's back. Static scan of sources and tsconfigs; no
4197-
# build needed (~1.2s), so it belongs in this job.
4198-
- name: Type-source resolution gate
4199-
run: pnpm check:type-source-resolution
4200-
42014175
# #4248 packaging-hygiene guard. Without a `files` whitelist npm packs the
42024176
# whole package directory, and 20 of the 49 publishable packages declared
42034177
# none — so consumers installed TypeScript sources, unit tests and build
@@ -5199,11 +5173,16 @@ jobs:
51995173

52005174
# The authorable KEY surface — what a metadata author may write, which for
52015175
# this platform is the third-party API. `api-surface/` records exported
5202-
# names and `api-surface-signatures.json` hashes factory types as TypeScript
5203-
# PRINTS them (a reference, never structurally expanded), so neither sees a
5204-
# key added to or removed from a schema. #3883 removed three authorable keys
5205-
# with every witness green; #3733 did it by accident. ADR-0059 §5 deferred
5206-
# this gate until a narrowing actually slipped both — it has.
5176+
# names only, so it does not see a key added to or removed from a schema;
5177+
# the shape sibling that used to sit beside it (`api-surface-signatures.json`,
5178+
# retired at #16045) hashed factory types as TypeScript PRINTS them — a
5179+
# reference, never structurally expanded — so it did not see one either.
5180+
# #3883 removed three authorable keys with every witness green; #3733 did it
5181+
# by accident. ADR-0059 §5 deferred this gate until a narrowing actually
5182+
# slipped both — it has. `check:api-surface-declarations` (consumer-gates
5183+
# lane) now records the declaration TEXT of every export, which DOES move on
5184+
# such a key; this gate stays the authority on the AUTHORABLE key set, which
5185+
# is a different question from the declared TypeScript shape.
52075186
#
52085187
# ⚠ ORDER: this step must stay ABOVE the `check:docs` step below. Its
52095188
# `--check` run of scripts/build-schemas.ts writes the gitignored
@@ -6125,6 +6104,29 @@ jobs:
61256104
- name: Check @objectstack/spec public API surface
61266105
run: pnpm --filter @objectstack/spec run check:api-surface
61276106

6107+
# [#16045] The SHAPE half of the same surface, and the step the card above
6108+
# exists for: `api-surface/` pins 5336 `name (kind)` rows and a signature
6109+
# change, a renamed interface field and a dropped union member move NONE of
6110+
# them, so 99.5% of the pinned surface could not go red on a breaking shape
6111+
# change to a ratified public type. The declaration-text snapshot records
6112+
# what the packed `.d.ts` actually declares for every export, per entry
6113+
# point. Ruled at #16045 (director batch #60, maintainer 「同意」): text and
6114+
# ⛔ NOT a hash, because a red hash gets accepted rather than investigated
6115+
# and a readable diff is what makes contract review a guard.
6116+
#
6117+
# WHY THIS LANE. It resolves each entry point through the `exports` map to
6118+
# the BUILT `.d.ts` — the declarations a consumer installs — so it is
6119+
# build-dependent and sits after the two build steps above with its family
6120+
# (`check:api-surface`, `check:published-readme-exports`). A missing or
6121+
# stale dist is a HARD REFUSAL in both of the script's modes, never a skip:
6122+
# a build-dependent gate that silently reads nothing reports "not measured"
6123+
# as if it were "measured and clean" (#4690).
6124+
#
6125+
# It adds no required context: a step in an existing lane, so no open PR
6126+
# waits on a check whose name no head has ever reported (#9325).
6127+
- name: Check @objectstack/spec declaration text (the shape half)
6128+
run: pnpm --filter @objectstack/spec run check:api-surface-declarations
6129+
61286130
# [#11350] Consumer-shaped declaration-emit pin against the BUILT root
61296131
# entry (an un-annotated `export default defineStack(...)` must compile
61306132
# with `declaration: true` — the TS2883 class). The pin is environment-

0 commit comments

Comments
 (0)