Skip to content

Commit 4fd5c35

Browse files
committed
Merge origin/main into claude/issue-24091-serve-runtime-state-silent
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb
2 parents c15bf91 + d3d0644 commit 4fd5c35

35 files changed

Lines changed: 1582 additions & 580 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/spec': patch
3+
---
4+
5+
The liveness ledger's `/meta` type-list, per-type-list and maintenance switch rows cite the code that reads their keys where it now lives
6+
7+
Clause-②: no (only the rows' evidence paths move and every status stays `live`: `MetadataEndpointsConfigSchema` accepts `endpoints.types`, `endpoints.items` and `endpoints.maintenance` exactly as before, and no state count moves)
8+
9+
- `liveness/metadata_endpoints.json`: the `endpoints.types`, `endpoints.items` and `endpoints.maintenance` rows cited `packages/rest/src/rest-server.ts#registerMetadataEndpointsInner` for the gates on their mounts. `@objectstack/rest` moved those three route groups to `packages/rest/src/meta-collection-routes.ts#registerMetaCollectionRoutes` with no behaviour change, so those three path references follow them.
10+
- The quoted code, the producers, the verdicts and the notes are unchanged. The `prefix` row keeps its citation: `metaPath` is still built in the inner method's head.
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
The `/meta` type list, whole-store maintenance and per-type list routes live in their own module, with no route, handler or answer changed
6+
7+
Clause-②: no (a move: the seven routes per base, their place in the `/meta` registration order, every handler, every response and the anonymous-deny gate in front of them are unchanged, and the published declarations are byte-identical)
8+
9+
- `RestServer#registerMetadataEndpointsInner` keeps its name, docblock, place and head. The statements that register `GET {metaPath}` and `GET {metaPath}/types`, `GET {metaPath}/diagnostics`, `GET {metaPath}/_drafts`, `POST {metaPath}/_migrate-stored` and `GET {metaPath}/:type` moved, re-indented, to `packages/rest/src/meta-collection-routes.ts`, and the method calls them at their old position, inside the umbrella's guarded registration. The `metadata.endpoints.types`, `.maintenance` and `.items` switches gate the same mounts.
10+
- Nothing to do for a consumer: no export of the package entry, no route, no configuration key and no response moved.
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+
fix(cli): `os migrate plan` no longer lists `sys_packages` among the tables "declared by no object in this plan" after `os migrate apply` has created it
6+
7+
Clause-②: no
8+
9+
`sys_packages` is the package registry's table. It is not an object: `PackageServicePlugin` creates it with its own DDL, and `os migrate apply` creates it from the registry's `package.storage` declaration. The plan's unmanaged-table sweep built its known set from the planned drivers' managed tables and the declared objects only, so on every applied deployment the plan reported the live registry table as if it were stranded.
10+
11+
- **Now:** the sweep also counts the tables the boot's composition declares outside the object set. It reads that name from the same `package.storage` declaration the plan creates the table from. It reads it on every boot, whether or not the table exists yet, the skip-schema-sync boot of a Turso remote primary included. On `examples/app-crm` after `os migrate apply --yes`, the plan lists only a genuinely stranded `sys_*` table.
12+
- **Still reported:** a `sys_packages` table in the database of a deployment that composes no package registry. Nothing there declares it.
13+
- **Refused in one more case:** a `package.storage` service whose shape this CLI does not read now fails `plan`, `apply` and `organization-ownership` on a skip-schema-sync boot too, with the error those commands already gave on every other boot: `@objectstack/cli` and `@objectstack/service-package` must come from the same release.
14+
- **Unchanged:** the `--json` keys, the section's wording, and what `apply` creates.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
---
2+
'@objectstack/plugin-auth': patch
3+
---
4+
5+
fix(plugin-auth): the MCP access-token verifier follows the signing keyring instead of holding its own copy of the key set for five minutes
6+
7+
Clause-②: no
8+
9+
- **What changed.** `AuthManager.verifyMcpAccessToken` no longer keeps its own copy of the signing key set for five minutes. It reads the key set on every call through the same keyring reader the token signer and `/jwks` use, so the set it checks a signature against is held only under both halves of the cross-request caching rule: a `sys_jwks` write that passes this process's data engine retires it in the same operation, and a 30-second bound covers a write the process cannot see, such as one made on another node.
10+
- **A token signed by a key the set does not carry gets one fresh read.** Before such a token is refused, the keyring is read once more from the table, and the token is checked again. That read is shared by every token that arrives while it is in flight, and a new one starts no sooner than 30 seconds after the previous one, so tokens that name keys which do not exist cost at most one keyring read per 30 seconds. A token whose key is still absent after that read is refused, as before.
11+
- **Unchanged.** Issuer, audience, expiry, the `client_credentials` refusal and the `organization_id` claim are checked exactly as before. Only the public key set `/jwks` publishes is used for verification. No export, configuration key or environment variable is added.

‎content/docs/permissions/system-context.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ One of the largest consumers of the flag;
181181
|:--|:---|:---|:---|:---|
182182
| 48 | Object API-exposure gate bypassed (`apiEnabled` / `apiMethods`) | runtime | Get: internal self-writes ignore exposure declarations — these govern **external** exposure, not engine self-writes. The gate is `refuseUnexposedDataAction`, which `callData` asks first and the `/data` domain asks before its request gate; `callData` reads the same bypass once more for each nested-relation condition's target | `packages/runtime/src/action-execution.ts#callData`, `#refuseUnexposedDataAction` |
183183
| 49 | Action `requiredPermissions` bypassed | runtime | Get: engine self-invocation runs any action | `packages/runtime/src/action-execution.ts#actionPermissionError` |
184-
| 50 | `manage_metadata` bypassed on metadata writes | runtime, rest | Get: schema writes without the capability (the permission-set clone door's save included) — and, because REST's save door and the stored-version read doors ask one save verdict, an app's full stored version on `/layers`, `?layers=true` and `/diff`, whatever its entry gates withhold | `packages/runtime/src/domains/meta.ts#handleMetadataRequest`, `packages/rest/src/rest-server.ts#registerMetadataEndpointsInner`, `packages/rest/src/rest-server.ts#metaSaveVerdict`, `packages/runtime/src/domains/permission-set-clone.ts#handlePermissionSetCloneWrite` |
184+
| 50 | `manage_metadata` bypassed on metadata writes | runtime, rest | Get: schema writes without the capability (the permission-set clone door's save included) — and, because REST's save door and the stored-version read doors ask one save verdict, an app's full stored version on `/layers`, `?layers=true` and `/diff`, whatever its entry gates withhold | `packages/runtime/src/domains/meta.ts#handleMetadataRequest`, `packages/rest/src/rest-server.ts#registerMetadataEndpointsInner`, `packages/rest/src/meta-collection-routes.ts#registerMetaCollectionRoutes`, `packages/rest/src/rest-server.ts#metaSaveVerdict`, `packages/runtime/src/domains/permission-set-clone.ts#handlePermissionSetCloneWrite` |
185185
| 51 | The shared metadata-write verdict itself returns `allowed` | metadata-core | Get: the one function all of row 50's doors consult answers yes before any capability is examined | `packages/metadata-core/src/meta-write-capability.ts#metaWriteCapabilityVerdict` |
186186
| 52 | Anonymous-deny seam satisfied on the domain dispatchers and the package/federation routes | runtime, rest | Get: passes with no `userId` | `packages/runtime/src/domains/actions.ts#handleActionsRequest`, `packages/runtime/src/domains/ai.ts#handleAIRequest`, `packages/runtime/src/domains/automation.ts#handleAutomationRequest`, `packages/runtime/src/domains/meta.ts#handleMetadataRequest`, `packages/runtime/src/domains/security.ts#handleSecurityRequest`, `packages/runtime/src/domains/packages.ts#handlePackagesRequest`, `packages/runtime/src/domains/analytics.ts#handleAnalyticsRequest`, `packages/runtime/src/domains/i18n.ts#handleI18nRequest`, `packages/rest/src/external-datasource-routes.ts#registerExternalDatasourceRoutes`, `packages/rest/src/package-routes.ts#refusePackageRequest` |
187187
| 53 | MCP principal check satisfied | runtime | Get: MCP surface reachable with no user | `packages/runtime/src/domains/mcp.ts#handleMcpRequest` |

‎docs/qa/platform-checklist/areas/api-backend.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2446,7 +2446,7 @@
24462446
"source": [
24472447
"packages/spec/liveness/metadata_endpoints.json (the ADR-0049 ledger this item answers: prefix, enableCache, maskObjectFields and three endpoints.* live, cacheTtl and endpoints.schema dead)",
24482448
"packages/spec/src/api/rest-server.zod.ts#MetadataEndpointsConfigSchema (the declared contract — four endpoints.* switches whose describe() strings enumerate every mount they gate since #15542/#15854, the ADR-0106 D8 docblock and the two retiredKey tombstones)",
2449-
"packages/rest/src/rest-server.ts#registerMetadataEndpointsInner (metaPath, the four endpoint gates and registerPerItemRoute — the per-item switch spelled as a call so the later members of that face carry it), (resolveObjectMasker — the metaType/maskObjectFields fork), (normalizeConfig — isObjectSchemaMaskingEnabled and the OS_ALLOW_UNMASKED_OBJECT_METADATA escape hatch), (getRoutes) + packages/rest/src/discovery-routes.ts#registerDiscoveryEndpoints (discovery.routes.metadata built from prefix)",
2449+
"packages/rest/src/rest-server.ts#registerMetadataEndpointsInner (metaPath, the item endpoint gate and registerPerItemRoute — the per-item switch spelled as a call so the later members of that face carry it), (resolveObjectMasker — the metaType/maskObjectFields fork), (normalizeConfig — isObjectSchemaMaskingEnabled and the OS_ALLOW_UNMASKED_OBJECT_METADATA escape hatch), (getRoutes) + packages/rest/src/meta-collection-routes.ts#registerMetaCollectionRoutes (the types, items and maintenance endpoint gates) + packages/rest/src/discovery-routes.ts#registerDiscoveryEndpoints (discovery.routes.metadata built from prefix)",
24502450
"ADR-0106 D8 (per-caller field masking of served object schemas) · #14691 (the two retirements) · #7526 (the /meta/types ordering fix the `types` switch takes with it) · ADR-0076 D12 · #15542 / #15854 (the switch-radius ruling that created `maintenance` and gave `item` its own writes) · #15543 (why the measured blast radius of that move is zero: no shipped boot path authors a RestServerConfig)",
24512451
"sibling clause: platform-core.metadata-registry-serving (it drives the served REGISTRY on the default config; this item covers the config that decides what is served and to whom) · platform-core.docs-audience-gate (the audience-gated types that are a cache carve-out here)"
24522452
],

‎docs/qa/platform-checklist/areas/studio-authoring.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1394,7 +1394,7 @@
13941394
"ref": "packages/metadata-protocol/src/protocol.diagnostics-store-outage.test.ts — pins the outage clause ONLY (503 rethrown, never '0 problems'); the live sweep, scoping parity, refusals, and both browser surfaces still need the run"
13951395
},
13961396
"source": [
1397-
"packages/rest/src/rest-server.ts#route (the route: registered BEFORE /meta/:type so 'diagnostics' is not captured as a type; severity defaults 'error'; ?type/?severity/?package each single-valued via refuseRepeatedQueryParams #6877; 501 NOT_IMPLEMENTED when the kernel's protocol lacks getMetaDiagnostics; answers res.json(result) BARE)",
1397+
"packages/rest/src/meta-collection-routes.ts#registerMetaCollectionRoutes (the route: registered BEFORE /meta/:type so 'diagnostics' is not captured as a type; severity defaults 'error'; ?type/?severity/?package each single-valued via refuseRepeatedQueryParams #6877; 501 NOT_IMPLEMENTED when the kernel's protocol lacks getMetaDiagnostics; answers res.json(result) BARE)",
13981398
"packages/rest/src/rest-route-ledger.ts#REST (REST-only route, bare body = GetMetaDiagnosticsResponseSchema, client meta.getDiagnostics, #12038)",
13991399
"packages/metadata-protocol/src/protocol.ts#getMetaDiagnostics (getMetaDiagnostics: registry-derived type set filtered to registered schemas; reuses the _diagnostics read decoration; stats { count, locked, packages } computed in the same sweep 'so the Studio directory page can render tile counts and a package filter in one round-trip'; 503 outage rethrown #8855 / ADR-0110 D3; 400 unrecognised-spelling rethrown #8924; warnings 'reserved for a future lint layer')",
14001400
"packages/metadata-protocol/src/metadata-diagnostics.ts#computeMetadataDiagnostics (computeMetadataDiagnostics — errors only, undefined = no opinion for schema-less types; zodIssuesToMetadataIssues names union branches #5598 so Studio has a path to highlight)",

‎packages/cli/src/commands/migrate/plan.boot-parity.integration.test.ts‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,14 @@ import Database from 'better-sqlite3';
4040
* included) — with `pending` empty, equal counts are equal sets;
4141
* - the unmanaged-tables sweep RUNS on every shape — the composition says it
4242
* mirrors the served boot, the compiled-artifact project with no config
43-
* included (#22580) — and names nothing but `sys_packages`.
43+
* included (#22580) — and names nothing: [#24059] not even `sys_packages`,
44+
* which the composed package registry declares outside the object set.
4445
*
4546
* `sys_packages` is the one table outside the object set, by construction:
4647
* `PackageServicePlugin.start()` creates it with raw DDL (`package-table.ts`),
47-
* and no object declares it. A new raw-DDL table fails this pin and has to be
48-
* named here with the same reason.
48+
* and no object declares it — its `package.storage` declaration does, and the
49+
* sweep reads that declaration. A new raw-DDL table fails this pin and has to
50+
* be named here with the same reason.
4951
*
5052
* Both sides run with `OS_TELEMETRY_DB=0`, so the pin compares object sets on
5153
* one database. A development boot's `telemetry` sibling (ADR-0057 §3.6) — a
@@ -145,10 +147,11 @@ function expectParity(r: Reading): void {
145147
// … and platform tables by name: [#22580] the unmanaged sweep runs on every
146148
// shape, because the composition records that it mirrors the served boot —
147149
// the compiled artifact with no config included, which it used to withhold
148-
// with a reason that no longer held.
150+
// with a reason that no longer held. [#24059] It reports nothing: the
151+
// raw-DDL table above exists, and its declaration accounts for it.
149152
expect(r.plan.unmanagedTables?.status, JSON.stringify(r.plan.unmanagedTables)).toBe('read');
150153
const unmanaged = (r.plan.unmanagedTables.tables ?? []).map((t: any) => t.table);
151-
expect(unmanaged).toEqual(NOT_AN_OBJECT);
154+
expect(unmanaged).toEqual([]);
152155
}
153156

154157
describe('os migrate plan examines exactly what os serve registers, per app shape (#22506)', () => {

‎packages/cli/src/commands/migrate/plan.telemetry-sibling.integration.test.ts‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -194,9 +194,10 @@ describe('os migrate plan / apply plan each object against the database it lives
194194
// Both databases' objects examined: their tables, less the raw-DDL one.
195195
expect(plan.payload.managedTables).toBe(primaryTables.length - NOT_AN_OBJECT.length + siblingTables.length);
196196
// [#22580] Both databases' catalogs swept: the sibling's stranded table is
197-
// reported beside the primary's raw-DDL one.
197+
// reported — and [#24059] the primary's raw-DDL one is not, its
198+
// declaration accounts for it.
198199
expect(plan.payload.unmanagedTables?.status, JSON.stringify(plan.payload.unmanagedTables)).toBe('read');
199-
expect(plan.payload.unmanagedTables.tables.map((t: any) => t.table)).toEqual([...NOT_AN_OBJECT, retired].sort());
200+
expect(plan.payload.unmanagedTables.tables.map((t: any) => t.table)).toEqual([retired]);
200201
// A plan writes nothing, to either database.
201202
expect({ primary: state(f.primary), sibling: state(f.sibling) }).toEqual(before);
202203

‎packages/cli/src/commands/migrate/plan.ts‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ export default class MigratePlan extends Command {
232232
// resolved from it are two connections to one file.
233233
drivers: distinctDatabaseDrivers(stack.drivers),
234234
declaredObjects: stack.allObjects(),
235+
// [#24059] …and the tables the composition declares outside the object
236+
// set — `sys_packages`, which `os migrate apply` creates.
237+
nonObjectTables: stack.nonObjectTables,
235238
composition: stack.composition,
236239
normalize: normalizeRows,
237240
});

0 commit comments

Comments
 (0)