Skip to content

Commit fb0af58

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-16249-refuse-create-at-lowering
2 parents 8683019 + afa3a26 commit fb0af58

57 files changed

Lines changed: 3832 additions & 404 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: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
---
2+
'@objectstack/cli': patch
3+
---
4+
5+
`os lint --eval --generator ""` no longer prints a double space in its refusal.
6+
7+
`bundle-require` composes its own refusal as `<filepath> is not a valid JS file`, so an
8+
empty filepath contributes no characters and that fragment arrives with a leading space —
9+
which landed against the space in our own `": "` separator:
10+
11+
```
12+
Failed to load generator "": is not a valid JS file # before, both faces
13+
Failed to load generator "": is not a valid JS file # after
14+
```
15+
16+
The composed message now drops leading spaces from the detail, so the separator carries
17+
exactly one. The empty string still answers through the same door an unresolvable path
18+
answers through — same `catch`, same exit code 1, same one-key `{error}` document on the
19+
`--json` face — and every refusal whose detail does not open with a space is byte-identical,
20+
the unresolvable-path case included.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
`objectNavTargetExclusivity` — the object-level check on an object navigation item that refuses `filters` combined with `recordId` / `viewName`, and `runAction` combined with `recordId` — is now EXPORTED from `@objectstack/spec/ui`, one function per refinement in the same posture as the `check*` exports. A hand-written mirror of the object nav item chains this very function in its own `superRefine` instead of restating the rule from prose; a restatement is what drifts: objectui's hand-written mirror re-implements neither rule — its `superRefine` checks only `id` / `label`, and the file names no `filters` rule beyond the field's declaration (measured at the pinned `.objectui-sha` and at objectui `origin/main`).
6+
7+
**What moves for consumers: one new export.** No schema's accept set moves. `NavigationItemSchema` chains the check exactly where it did — its `type: 'object'` branch — and the exported `ObjectNavItemSchema` still does not chain it: which schema mounts the check is a separate question from whether a mirror can, and it is not decided here. The two deliberate asymmetries are unchanged and now pinned: `recordId` + `viewName` stays a tolerated legacy combination, and `runAction` is refused with `recordId` only (it still composes with `filters` / `viewName`).
8+
9+
**Also corrected, in the same file:** the `filters` docblock stated a complete precedence order (`recordId``filters``viewName`) a few lines above saying the combination is unrepresentable, and the mirror copied that first half. The docblock now states only what the guard refuses, says in as many words that no precedence order is stated and why, and names the one legacy combination the guard tolerates. The `.describe()` strings — what reaches the generated references — are unchanged.
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
---
2+
"@objectstack/cli": patch
3+
---
4+
5+
`os i18n extract --source-hashes` no longer writes a provenance companion with no bundle module beside it, and names the sections it commits from the payloads those modules hold instead of from two literals.
6+
7+
The command narrows the provenance table to "the sections this run commits" before writing `<locale>.source-hashes.generated.ts`. The half that decided WHICH modules were emitted already read the emitted set; the half that named them pushed the string `'objects'` or `'metadataForms'`.
8+
9+
- **A zero-record orphan is no longer written.** With no module emitted for a locale — a stack whose only surface is apps, under the default `--objects-only` with `--no-metadata-forms` — the committed-section list is empty, `narrowToCommittedSections` returns `{}`, and `{}` is truthy at the emit gate. The run therefore wrote one file holding an empty table, describing nothing, with no bundle module beside it for it to be about. Because `--check` compares the companion by bytes like any other emitted file, that orphan once committed is a file the gate demands forever: deleting it made `--check` report `missing` and exit 1. Such a run now writes nothing, and reports `Generated 0 file(s)`.
10+
- **The section list is derived.** `translationModuleSections(bundle, kind)` sits beside `translationModulePayload` and is switched on the same `kind`, so what a module holds and which sections it commits are one decision rather than two. Under `kind: 'stack'` the module holds every group the stack authors and the caller now names all of them; a group added later needs no edit, and a further aggregate kind fails to compile at that one site rather than silently committing its own name as a section.
11+
12+
**No provenance record changes in this repository, and none is restored.** The generated tables only ever carry the two sections `collectFilledFromHashes` walks (`objects` and `metadataForms`), so `'objects'` was the right name for both stack sub-tree modes — the old list was correct by coincidence, not by construction. In particular an `apps.*` record is not restored by this change: no such record is built, so none was being filtered out.
13+
14+
**Already committed an empty companion?** Nothing needs doing and nothing is deleted. `--check` compares only the files a run writes and reports `missing` / `stale` over that set, so a leftover empty companion is in neither category — it is tolerated where it sits, and is inert to the next extract, which reads it back as an empty record set exactly as it would read its absence. Delete it at your convenience.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@objectstack/plugin-auth": minor
3+
---
4+
5+
MCP OAuth can complete again: the MCP resource is registered as an RFC 8707 resource and DCR-registered clients are linked to it, so `authorize?resource=<mcp url>` no longer answers `invalid_target`.
6+
7+
On 17.3.0 no MCP client could ever obtain a token. `plugin-auth` configured `@better-auth/oauth-provider` with `validAudiences: [authIssuer, mcpResourceUrl]`, an option the pinned 1.7.2 does not read — the string does not occur once in its dist. In 1.7.2 a requested `resource` is resolved from the `oauthResource` table (`sys_oauth_resource`) and `enforcePerClientResources` defaults to `true`, so the client must also be linked in `oauthClientResource` (`sys_oauth_client_resource`). Neither row was ever written, so every client that sends `resource=` — Claude Code does — was refused at `/oauth2/authorize` with `invalid_target: requested resource <mcp url> is not configured`. Discovery, dynamic client registration and the login page all worked; the flow died one step before consent.
8+
9+
- **`resources: [mcpResourceUrl]`** seeds the `sys_oauth_resource` row from the provider's own `init`. Seeding is idempotent and defaults to `insertOnly`, so an administrator's later edits to the row's token policy are never reverted by a restart.
10+
- **`clientRegistrationDefaultResources: [mcpResourceUrl]`** links each newly registered client to that resource inside the DCR transaction. This is the only place the link can be made: a client registers anonymously about one second before the browser login, leaving no window for an administrator to insert the row by hand.
11+
- **`enforcePerClientResources` is left at its `true` default.** The per-client linkage check stays on — the fix makes the link exist rather than switching the check off. A client with no link row is still refused with `invalid_target`, and a test asserts that.
12+
- **`validAudiences` is removed.** It was passed and read by nobody, which is precisely how the defect survived a version bump: it looked like configuration and enforced nothing.
13+
14+
Two boot-path defects the resource seed uncovered are fixed in the same change, because seeding is the first thing this package ever wrote from a plugin `init`:
15+
16+
- **`getAuthInstance()` now settles better-auth's plugin `init` hooks before it resolves.** `betterAuth()` returns synchronously and runs those hooks behind `auth.$context`, so a failure inside one had no catcher and escaped as an unhandled rejection — which Node terminates the process for by default. A boot failure now rejects the call that asked for the instance.
17+
- **The no-`dataEngine` development fallback builds its own in-memory adapter instead of letting better-auth build one.** better-auth 1.7.2 keys that store by the schema *key* while every read resolves by `modelName`, so on that path every model this package renames was unreachable — `user`/`sys_user` as much as `oauthResource`/`sys_oauth_resource` — answering `Model <name> not found`. Production never took this branch (it uses the ObjectQL adapter); development and tests did.
18+
19+
No configuration change is required. Deployments that already ran 17.3.0 get the resource row on the next boot; MCP clients that failed to connect need to reconnect so a fresh registration picks up the link.
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/core": patch
3+
"@objectstack/cli": patch
4+
---
5+
6+
The `Server is ready` line now reports the degraded boot it is standing on, instead of printing a green `` over it.
7+
8+
`✓ Server is ready` and the kernel's `System started with degraded capabilities. Missing core services: …` were two statements about one boot, produced by two packages — the banner in `@objectstack/cli`, the conclusion in `@objectstack/core` — with **no data path between them**. So the ready signal did not depend on the thing that broke, and therefore could not report it. Measured twice within a day, from unrelated causes: an objectui CI boot where the auth plugin failed and not one `sys_*` table existed, and this repo's own weekly registry canary on the published `npx create-objectstack@latest` on-ramp, where the tick printed directly **above** four boot warnings. In the second case the ready line carried no weight in the job's verdict at all — it was present, green, wrong, and believed by nobody.
9+
10+
- **The data path.** `ObjectKernel.validateSystemRequirements()` now publishes the list it had already computed — the same array behind its own warning — on the kernel's service registry, which is the seam boot facts already cross to reach the banner (`serve` reads `auth` and `seed-summary` off it the same way). No member and no type is added to `@objectstack/core`'s public surface, and nothing re-derives which services count as `core`: that judgement stays in `ServiceRequirementDef` alone.
11+
- **The line.** On a degraded boot the banner prints `⚠ Server is ready — DEGRADED: missing core services: <names>`, naming exactly what the kernel found missing. On a healthy boot the ready block is byte-for-byte unchanged, so an ordinary boot's output does not move.
12+
- **Readiness is NOT made strict.** Nothing about what boots, binds, or exits changes. A machine deliberately running without auth still starts, still prints ready, and still exits 0 — the line just says what state it is ready in.
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
feat(spec): register the fourteen remaining `door: 'none'` error codes that ship in `dist` — the rest of the #16404 class after #16449 enters `ERROR_CODE_LEDGER` (#16649)
6+
7+
Under the #16404 ruling (director seat, decision batch #62, 2026-09-07, option D; maintainer 「同意」) **the published contract face for error codes is `ERROR_CODE_LEDGER` / `StandardErrorCode`**: every `code` that ships in a package's `dist` is registered there, door or no door, because a consumer's `catch (e) { switch (e.code) }` pins the spelling the moment it ships. #16449 registered the nine codes measured on its tree; fourteen more were still shipping unregistered — every `boot-refusal` row `dispatcher-error-vocabulary.ts` carried — and now have rows, each under the package that stamps it:
8+
9+
| code | stamped by | `status` on the thrown value | reaches an HTTP door on this tree? |
10+
|---|---|---|---|
11+
| `INVALID_ARTIFACT_PACKAGES` · `INVALID_ARTIFACT_PACKAGE_ENTRY` · `DUPLICATE_ARTIFACT_PACKAGE` | `@objectstack/core` (`resolveArtifactPackageOrder`, ADR-0130 D4/D5) | 422 | no — boot-time `manifest.register()` aborts boot; the install route answers with its own `PLUGIN_REGISTER_FAILED` |
12+
| `NO_SUCH_RUN` · `PLAN_CHANGED` · `PREFLIGHT_FAILED` · `NOT_COMPENSABLE` | `@objectstack/core` (`MigrationJournalRefusal`, the migration-journal runner) | none | no — caught by the CLI's `migrate` commands with `instanceof` and printed |
13+
| `SERVICE_NOT_REGISTERED` | `@objectstack/core` (`PluginLoader.getService`'s "never registered" rejection) | none, by design | no — read in-process by the seam that catches the rejection |
14+
| `PLUGIN_CONTRACT_VIOLATION` | `@objectstack/core` (`assertPluginContract`, raised at `kernel.use()`) | none | no — raised while the kernel is still registering plugins |
15+
| `MIXED_ARTIFACT_COLLECTION_SHAPE` | `@objectstack/runtime` (`resolveArtifactCollections`, ADR-0130 D4) | 422 | no — every call site resolves before a transport exists |
16+
| `DUPLICATE_ARTIFACT_OBJECT_NAME` | `@objectstack/objectql` (`SchemaRegistry.installPackage`, ADR-0130 D3) | 422 | no — the HTTP install sites pass no artifact scope, so they cannot raise it |
17+
| `MEMORY_MULTI_TENANT_UNSUPPORTED` | `@objectstack/driver-memory` (the tenancy guard) | none | no — a boot refusal the CLI rethrows pre-HTTP |
18+
| `MONGODB_MULTI_TENANT_UNSUPPORTED` | `@objectstack/driver-mongodb` (the tenancy guard) | none | no — a boot refusal the CLI rethrows pre-HTTP (registered by #3724, unregistered by #8035, re-registered here under the ruling) |
19+
| `WALLED_MEMBERSHIP_POLICY_UNDECLARED` | `@objectstack/organizations` (the walled-posture membership-policy gate, `kernel:bootstrapped`) | none | no — fires before `kernel:listening` opens the socket |
20+
21+
**Wire consequence, stated plainly.** Registration changes what a client reads only where a code reaches an HTTP door: `error.code` would carry the specific code instead of the standard member the status derives, with the producer's spelling no longer demoted into `declaredCode`. Re-measured on this tree at the sites each `boot-refusal` row named (the table's last column is that reading, one line per group), **none of the fourteen has such a door**, so **no HTTP body changes with this release**. What changes is the face: `ErrorCode` — the union `ApiErrorSchema.code` parses against — gains fourteen members, `REGISTERED_ERROR_CODES` lists them, the ledger gains two owner keys (`@objectstack/driver-mongodb` returns after #8035 removed it; `@objectstack/organizations` is new), and each refusal's `e.code` is now a member of the union a consumer's exhaustive `switch` is written over. Should a door ever answer with one of them, the wire carries the specific code from then on.
22+
23+
**`MONGODB_MULTI_TENANT_UNSUPPORTED` is a deliberate reversal, not drift.** #8035 unregistered it on the ground that "host boot matching is not wire vocabulary"; the #16404 ruling supersedes exactly that ground (the ledger header's "Retiring a code" section records both halves), and the test that pinned its absence now pins its presence. What still retires a row is a code with no producer left in `packages/**``OVERLAY_PERSISTENCE_FAILED` (#5783) remains the pinned witness of that class.
24+
25+
**Why `minor`, and no `BREAKING` banner.** Nothing is removed or renamed; every existing body parses exactly as before. The change is a purely additive widening of a published surface (fourteen new `ErrorCode` members, two new owner keys), which the 2026-09-04 ruling on #15294 requires to be at least `minor`. The one consumer-visible cost is type-level: an exhaustive `switch` over the `ErrorCode` TYPE gains fourteen cases to cover.
26+
27+
The fourteen `boot-refusal` classification rows in `dispatcher-error-vocabulary.ts` ratchet out with the registrations (the gate reports a registered code's row as `stale-row`), their reachability reading now carried on the ledger rows; that module is not part of `@objectstack/runtime`'s published entry, so nothing in that package's `dist` moves. The `boot-refusal` verdict itself stays declared for a future pre-HTTP producer; retiring it and widening the gate's spec-face refusal to every published package is the card's second half and is not in this release.

0 commit comments

Comments
 (0)