From 323ac52449000e7d8f7b537937696aedfa0c6b6b Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 15:59:37 +0000 Subject: [PATCH 1/2] =?UTF-8?q?refactor(spec)!:=20=E9=80=80=E5=BD=B9=20`Ht?= =?UTF-8?q?tpServerConfigSchema`=20=E2=80=94=E2=80=94=20=E4=B9=9D=E4=B8=AA?= =?UTF-8?q?=E9=94=AE=E9=9B=B6=20reader=20=E4=B8=94=E6=B2=A1=E6=9C=89?= =?UTF-8?q?=E4=BB=BB=E4=BD=95=E4=BD=9C=E8=80=85=E9=9D=A2=E5=85=A5=E5=8F=A3?= =?UTF-8?q?=20(#4938)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `system/http-server.zod.ts` 的 `HttpServerConfigSchema` 声明九个键 (`port` / `host` / `cors` / `requestTimeout` / `bodyLimit` / `compression` / `security` / `static` / `trustProxy`),`authorable-surface.json` 全部在册、 `content/docs/references/` 全部渲染成协议文档。两头都是空的: 1. **零 runtime reader** —— 三个仓(objectstack / cloud / objectui)里没有任何 包用它解析过文档或读过它的键;spec 之外唯一的命中是 `shared/http.zod.ts` 里指回来的 "Used by:" 注释。 2. **零作者面入口** —— 比普通的「写得下去、不生效」更彻底。`stack.zod.ts` 没有 `server:` 键,`config-schema.json` 里零命中,也没有 settings manifest 承载它,所以文档承诺的这套配置连**写下去**都做不到。 按 ADR-0049 enforce-or-remove 与 2026-08-04 裁决,退役这个不可达面。 退役形态是**容器,不是整个文件**:`RouteHandlerMetadata`(`packages/rest` 消费)与 `MiddlewareType` / `MiddlewareConfig`(`packages/runtime` 消费) 留下;`shared/http.zod.ts` 的 `CorsConfigSchema` / `RateLimitConfigSchema` / `StaticMountSchema` 各自另有 live consumer,也未被孤立。 **不打 `retiredKey()` tombstone**(playbook 路线 3,#4834 / PR #4878 同形): tombstone 是给「写下这个键的人」的话,而唯一能写 server 键的面是 #5006 的 `StackServerConfigSchema`,它是 `strictObject`,七个键早已按名拒绝并各带处方 —— 本 PR 把那些处方从「no runtime reads it」刷新为指明退役与替代。**不注册 D2 conversion**:没有任何作者源需要改写。代码消费者的通道是 `api-surface.json` (−3)接 release-time 的 `spec-changes.json` diff,加上 changeset。 `cors` 按裁决登记为 `server:` 窄形状的**首个逐键准入候选**(嵌入是真场景), 届时按 #4910 范式键与执行器一并到位,不以死键形态占导出面。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB --- .changeset/http-server-config-retired.md | 73 ++++++++++ .../docs/getting-started/quick-reference.mdx | 2 +- .../docs/references/system/http-server.mdx | 29 +--- .../docs/references/system/stack-server.mdx | 28 +++- ...07-unknown-key-strictness-ledger.counts.md | 2 +- packages/spec/api-surface.json | 3 - packages/spec/authorable-surface.json | 9 -- packages/spec/json-schema.manifest.json | 1 - packages/spec/src/shared/http.zod.ts | 27 +++- packages/spec/src/stack.zod.ts | 13 +- packages/spec/src/system/http-server.test.ts | 97 ++++++++----- packages/spec/src/system/http-server.zod.ts | 135 ++++++++---------- packages/spec/src/system/stack-server.test.ts | 6 + packages/spec/src/system/stack-server.zod.ts | 44 ++++-- 14 files changed, 288 insertions(+), 181 deletions(-) create mode 100644 .changeset/http-server-config-retired.md diff --git a/.changeset/http-server-config-retired.md b/.changeset/http-server-config-retired.md new file mode 100644 index 0000000000..4bbc9095e0 --- /dev/null +++ b/.changeset/http-server-config-retired.md @@ -0,0 +1,73 @@ +--- +"@objectstack/spec": major +--- + +refactor(spec)!: retire `HttpServerConfigSchema` — nine documented keys with zero readers AND no way to write them (#4938) + +`system/http-server.zod.ts` declared `HttpServerConfigSchema` with nine keys — +`port`, `host`, `cors`, `requestTimeout`, `bodyLimit`, `compression`, +`security`, `static`, `trustProxy`. `authorable-surface.json` listed all nine +and `content/docs/references/` rendered them as protocol documentation. Both +halves of the contract were empty: + +- **Zero runtime readers.** No package in any repo (objectstack / cloud / + objectui) ever parsed a document with this schema or read a key off it. The + only non-spec mentions were "Used by:" comments in `shared/http.zod.ts` + pointing back at it. +- **Zero authoring entry** — worse than the ordinary declared-but-unread + defect. `stack.zod.ts` had no `server:` key, `config-schema.json` had no + `HttpServerConfig`, and no settings manifest carried it, so the configuration + the docs promised could not even be written down, let alone take effect. + +What actually decides these things is three *other* shapes: the CLI `serve` +arguments, the Hono adapter's `ObjectStackHonoOptions`, and +`DispatcherPluginConfig.securityHeaders`. `HttpServerConfigSchema` was +unacquainted with all three. Per ADR-0049 enforce-or-remove, and the 2026-08-04 +ruling on #4938, the unreachable face is removed. + +FROM → TO, per retired key: + +| removed | what to do instead | +|---|---| +| `HttpServerConfig.port` / `.host` | the deployment owns the socket — `objectstack serve -p ` / `PORT` | +| `HttpServerConfig.static` | the transport plugin's `staticMounts` | +| `HttpServerConfig.cors` | the transport adapter — `OS_CORS_ORIGIN` / `OS_CORS_CREDENTIALS` / `OS_CORS_MAX_AGE` | +| `HttpServerConfig.security.helmet` | the dispatcher plugin's `securityHeaders` (on by default) | +| `HttpServerConfig.security.rateLimit` | `defineStack({ server: { security: { rateLimit } } })` — LIVE since #5006 | +| `HttpServerConfig.trustProxy` | `defineStack({ server: { trustProxy } })` — LIVE since #5006 | +| `HttpServerConfig.requestTimeout` / `.bodyLimit` / `.compression` | nothing consumes them; they return with an executor or not at all | + +Two of the nine were **activated** rather than lost: #5006 mounted +`security.rateLimit` and `trustProxy` on the deliberately narrow +`StackServerConfigSchema`, which grows one key at a time, each arriving with its +consumer. `cors` is registered as the FIRST per-key admission candidate for that +shape — embedding (`example-embed-objectql`) is a real scenario — and will +arrive the #4910 way, key and executor together, rather than sitting on the +export surface as a dead declaration in the meantime. + +The retirement kit: + +- **No `retiredKey()` tombstone, deliberately** — route 3 of the retirement + playbook ("nothing parses it"), the shape #4834 / PR #4878 used for the kernel + plugin-runtime family. A tombstone is a message to whoever writes the key, and + the only surface on which anyone can write a server key is + `StackServerConfigSchema`; it is `strictObject` and already rejects all seven + by name. Those prescriptions were refreshed from "not authorable — no runtime + reads it" to name the retirement and its replacement. +- **No ADR-0087 D2 conversion**, for the same reason: there is no author source + to rewrite, because the shape was never reachable from an authoring surface. + The channel for code consumers is `api-surface.json` (which lost all three + `HttpServerConfig*` entries) feeding the release-time `spec-changes.json` + diff, plus this changeset. +- Baselines updated deliberately: `json-schema.manifest.json` (−1, the #2978 + ratchet fired first and demanded it), `authorable-surface.json` (−9, allowed + by the #4650 gate's path 3 "def no longer emitted by this build"), + `api-surface.json` (−3). Reference docs and the strictness-ledger counts + regenerated. +- **The container, not the file.** `RouteHandlerMetadata` (consumed by + `packages/rest`) and `MiddlewareType` / `MiddlewareConfig` (consumed by + `packages/runtime`) stay, as do `CorsConfigSchema`, `RateLimitConfigSchema` + and `StaticMountSchema` in `shared/http.zod.ts` — each has live consumers + outside the retired shape, so none of them was orphaned by it. + +No runtime behaviour changes — that impossibility is the reason for the removal. diff --git a/content/docs/getting-started/quick-reference.mdx b/content/docs/getting-started/quick-reference.mdx index 708992f028..1673d11013 100644 --- a/content/docs/getting-started/quick-reference.mdx +++ b/content/docs/getting-started/quick-reference.mdx @@ -88,7 +88,7 @@ Runtime environment, logging, jobs, caching, and observability. | **[Change Management](/docs/references/system/change-management)** | `change-management.zod.ts` | ChangeRequest, RollbackPlan | Change tracking | | **[Collaboration](/docs/references/system/collaboration)** | `collaboration.zod.ts` | Collaboration | Real-time collab | | **[Encryption](/docs/references/system/encryption)** | `encryption.zod.ts` | Encryption | Encryption & keys | -| **[HTTP Server](/docs/references/system/http-server)** | `http-server.zod.ts` | HttpServerConfig, MiddlewareConfig | HTTP server config | +| **[HTTP Server](/docs/references/system/http-server)** | `http-server.zod.ts` | RouteHandlerMetadata, MiddlewareConfig | Route + middleware metadata | | **[Job](/docs/references/system/job)** | `job.zod.ts` | Job, JobSchedule | Background job queue | | **[Logging](/docs/references/system/logging)** | `logging.zod.ts` | LoggingConfig | Structured logging | | **[Message Queue](/docs/references/system/message-queue)** | `message-queue.zod.ts` | MessageQueueConfig, TopicConfig | Message queuing | diff --git a/content/docs/references/system/http-server.mdx b/content/docs/references/system/http-server.mdx index 49616bb6c2..6b3e15e956 100644 --- a/content/docs/references/system/http-server.mdx +++ b/content/docs/references/system/http-server.mdx @@ -7,9 +7,7 @@ description: Http Server protocol schemas HTTP Server Protocol -Defines the runtime HTTP server configuration and capabilities. - -Provides abstractions for HTTP server implementations (Express, Fastify, Hono, etc.) +Route-registration metadata, middleware declaration and the server-side lifecycle/status vocabulary for HTTP server implementations (Express, Fastify, Hono, etc.) Architecture alignment: @@ -26,32 +24,13 @@ Architecture alignment: ## TypeScript Usage ```typescript -import { HttpServerConfigSchema, MiddlewareConfigSchema, MiddlewareType, RouteHandlerMetadataSchema, ServerCapabilitiesSchema, ServerEventSchema, ServerEventType, ServerStatusSchema } from '@objectstack/spec/system'; -import type { HttpServerConfig, MiddlewareConfig, MiddlewareType, RouteHandlerMetadata, ServerCapabilities, ServerEvent, ServerEventType, ServerStatus } from '@objectstack/spec/system'; +import { MiddlewareConfigSchema, MiddlewareType, RouteHandlerMetadataSchema, ServerCapabilitiesSchema, ServerEventSchema, ServerEventType, ServerStatusSchema } from '@objectstack/spec/system'; +import type { MiddlewareConfig, MiddlewareType, RouteHandlerMetadata, ServerCapabilities, ServerEvent, ServerEventType, ServerStatus } from '@objectstack/spec/system'; // Validate data -const result = HttpServerConfigSchema.parse(data); +const result = MiddlewareConfigSchema.parse(data); ``` ---- - -## HttpServerConfig - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **port** | `integer` | ✅ | Port number to listen on | -| **host** | `string` | ✅ | Host address to bind to | -| **cors** | `{ enabled: boolean; origins: string \| string[]; methods?: Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>[]; credentials: boolean; … }` | optional | CORS configuration | -| **requestTimeout** | `integer` | ✅ | Request timeout in milliseconds | -| **bodyLimit** | `string` | ✅ | Maximum request body size | -| **compression** | `boolean` | ✅ | Enable response compression | -| **security** | `{ helmet: boolean; rateLimit?: object }` | optional | Security configuration | -| **static** | `{ path: string; directory: string; cacheControl?: string }[]` | optional | Static file serving configuration | -| **trustProxy** | `boolean` | ✅ | Trust X-Forwarded-* headers | - - --- ## MiddlewareConfig diff --git a/content/docs/references/system/stack-server.mdx b/content/docs/references/system/stack-server.mdx index 7b9687a695..2d8582e960 100644 --- a/content/docs/references/system/stack-server.mdx +++ b/content/docs/references/system/stack-server.mdx @@ -9,9 +9,9 @@ description: Stack Server protocol schemas ## Why this is NOT `HttpServerConfigSchema` -`[system/http-server.zod.ts](/docs/references/system/http-server)` declares nine keys (`port`, `host`, `cors`, +`[system/http-server.zod.ts](/docs/references/system/http-server)` used to declare nine keys (`port`, `host`, -`requestTimeout`, `bodyLimit`, `compression`, `security`, `static`, +`cors`, `requestTimeout`, `bodyLimit`, `compression`, `security`, `static`, `trustProxy`). #4938 measured them: **none had a runtime reader and none was @@ -37,11 +37,29 @@ consumer. Today that is exactly two: | `trustProxy` | the same limiter's IP resolution — see below | -The other seven `HttpServerConfigSchema` keys stay unreachable, and their +The other seven `HttpServerConfigSchema` keys were RETIRED with the shape -enforce-or-remove fate is tracked by #4938. Adding one here without an +that carried them (#4938, ADR-0049 enforce-or-remove): unreachable *and* -executor re-opens the hole this narrowness exists to close. +unread, they were the cleanest remove candidate in the ledger, and their + +prescriptions now live in the `guidance` maps below — the only place an + +author can write a server key is also the only place that has to answer for + +one. Adding one here without an executor re-opens the hole this narrowness + +exists to close. + +`cors` is the registered exception-in-waiting: the 2026-08-04 ruling named it + +the FIRST per-key admission candidate for this shape, because embedding + +(`example-embed-objectql`) is a real scenario with real pull. When that work + +is scheduled it arrives the #4910 way — key and executor in one change — not + +by un-retiring a declaration. ## What `server:` is NOT for diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md index b9de033020..156d45ec6a 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md @@ -280,4 +280,4 @@ directory rather than per file. | `kernel/` | 319 | | `qa/` | 6 | | `shared/` | 25 | -| `system/` | 368 | +| `system/` | 366 | diff --git a/packages/spec/api-surface.json b/packages/spec/api-surface.json index 776470ed2c..b6c05a69d3 100644 --- a/packages/spec/api-surface.json +++ b/packages/spec/api-surface.json @@ -944,9 +944,6 @@ "HistogramBucketConfigSchema (const)", "HttpDestinationConfig (type)", "HttpDestinationConfigSchema (const)", - "HttpServerConfig (type)", - "HttpServerConfigInput (type)", - "HttpServerConfigSchema (const)", "ISettingsCapability (interface)", "ISettingsClient (interface)", "Incident (type)", diff --git a/packages/spec/authorable-surface.json b/packages/spec/authorable-surface.json index 5703c2b88a..36dc0febfe 100644 --- a/packages/spec/authorable-surface.json +++ b/packages/spec/authorable-surface.json @@ -5911,15 +5911,6 @@ "system/HttpDestinationConfig:retry", "system/HttpDestinationConfig:timeout", "system/HttpDestinationConfig:url", - "system/HttpServerConfig:bodyLimit", - "system/HttpServerConfig:compression", - "system/HttpServerConfig:cors", - "system/HttpServerConfig:host", - "system/HttpServerConfig:port", - "system/HttpServerConfig:requestTimeout", - "system/HttpServerConfig:security", - "system/HttpServerConfig:static", - "system/HttpServerConfig:trustProxy", "system/Incident:affectedDataClassifications", "system/Incident:affectedSystems", "system/Incident:category", diff --git a/packages/spec/json-schema.manifest.json b/packages/spec/json-schema.manifest.json index b3a1946f84..102175edcd 100644 --- a/packages/spec/json-schema.manifest.json +++ b/packages/spec/json-schema.manifest.json @@ -1257,7 +1257,6 @@ "system/GCounter", "system/HistogramBucketConfig", "system/HttpDestinationConfig", - "system/HttpServerConfig", "system/Incident", "system/IncidentCategory", "system/IncidentNotificationMatrix", diff --git a/packages/spec/src/shared/http.zod.ts b/packages/spec/src/shared/http.zod.ts index 4a51f5740f..8c64374c10 100644 --- a/packages/spec/src/shared/http.zod.ts +++ b/packages/spec/src/shared/http.zod.ts @@ -63,8 +63,13 @@ export type HttpRequest = z.infer; * * Used by: * - api/router.zod.ts (RouterConfigSchema) - * - system/http-server.zod.ts (HttpServerConfigSchema) - * + * + * (`system/http-server.zod.ts` embedded this as `HttpServerConfig.cors` until + * #4938 retired that shape. CORS is owned by the transport adapter and + * configured by OS_CORS_ORIGIN / OS_CORS_CREDENTIALS / OS_CORS_MAX_AGE; this + * schema is the registered first candidate for a future `server.cors` key, + * which arrives WITH its executor or not at all.) + * * @example * { * "enabled": true, @@ -115,8 +120,13 @@ export type CorsConfig = z.infer; * * Used by: * - api/endpoint.zod.ts (ApiEndpointSchema) - * - system/http-server.zod.ts (HttpServerConfigSchema) - * + * - system/stack-server.zod.ts (ServerRateLimitConfigSchema — this shape reused + * verbatim and closed against unknown keys; the LIVE inbound token bucket) + * + * (`system/http-server.zod.ts` embedded this as `HttpServerConfig.security + * .rateLimit` until #4938 retired that shape; the budget itself was not lost — + * #5006 activated it on the narrow `server:` block.) + * * @example * { * "enabled": true, @@ -152,9 +162,12 @@ export type RateLimitConfig = z.infer; * Configuration for serving static files * * Used by: - * - api/router.zod.ts (RouterConfigSchema) - * - system/http-server.zod.ts (HttpServerConfigSchema) - * + * - api/router.zod.ts (RouterConfigSchema — `staticMounts`) + * + * (`system/http-server.zod.ts` embedded this as `HttpServerConfig.static` until + * #4938 retired that shape. Static mounts are configured on the transport + * plugin's `staticMounts`.) + * * @example * { * "path": "/static", diff --git a/packages/spec/src/stack.zod.ts b/packages/spec/src/stack.zod.ts index a65cb33b84..aad58042bb 100644 --- a/packages/spec/src/stack.zod.ts +++ b/packages/spec/src/stack.zod.ts @@ -364,12 +364,13 @@ export const ObjectStackDefinitionSchema = lazySchema(() => z.object({ * * DELIBERATELY NARROW (#4910): it carries only keys an executor consumes — * today `security.rateLimit` (the inbound token bucket that answers 429) and - * `trustProxy` (how that limiter identifies a caller). It is NOT the nine-key - * `HttpServerConfigSchema`: seven of those keys have no reader and no - * authoring surface, and mounting them here would make dead keys writable - * (their enforce-or-remove fate is #4938). Port/host stay a deployment - * concern owned by `objectstack serve -p`; see the schema file for the - * precedence rule and the rest of the rationale. + * `trustProxy` (how that limiter identifies a caller). It is NOT the former + * nine-key `HttpServerConfigSchema`: seven of those keys had no reader and no + * authoring surface, and mounting them here would have made dead keys + * writable — so they were retired with their container instead (#4938, + * ADR-0049). Port/host stay a deployment concern owned by + * `objectstack serve -p`; see the schema file for the precedence rule, the + * per-key prescriptions and the rest of the rationale. */ server: StackServerConfigSchema.optional() .describe('Server-level runtime config consumed by objectstack serve/dev (inbound rate limit, proxy trust)'), diff --git a/packages/spec/src/system/http-server.test.ts b/packages/spec/src/system/http-server.test.ts index 678fb2fdc7..ca71b8454e 100644 --- a/packages/spec/src/system/http-server.test.ts +++ b/packages/spec/src/system/http-server.test.ts @@ -1,6 +1,6 @@ import { describe, it, expect } from 'vitest'; +import * as httpServer from './http-server.zod'; import { - HttpServerConfigSchema, RouteHandlerMetadataSchema, MiddlewareType, MiddlewareConfigSchema, @@ -9,44 +9,73 @@ import { ServerCapabilitiesSchema, ServerStatusSchema, } from './http-server.zod'; - -describe('HttpServerConfigSchema', () => { - it('should accept minimal config with defaults', () => { - const config = HttpServerConfigSchema.parse({}); - - expect(config.port).toBe(3000); - expect(config.host).toBe('0.0.0.0'); - expect(config.requestTimeout).toBe(30000); - expect(config.bodyLimit).toBe('10mb'); - expect(config.compression).toBe(true); - expect(config.trustProxy).toBe(false); +import * as sharedHttp from '../shared/http.zod'; +import { RouterConfigSchema } from '../api/router.zod'; +import { ApiEndpointSchema } from '../api/endpoint.zod'; + +/** + * `HttpServerConfigSchema` was retired in v17 (#4938, ADR-0049 + * enforce-or-remove): nine keys, zero runtime readers, and — the condition that + * made it worse than the ordinary declared-but-unread defect — zero authoring + * entry, so the configuration the docs promised could not even be written down. + * + * These are the removal's pin tests. There is deliberately NO `retiredKey()` + * tombstone to assert against: a tombstone is a message to whoever writes the + * key, and the only surface on which anyone can write a server key is + * `StackServerConfigSchema`, which is `strictObject` and already answers for + * all seven by name. Those prescriptions are pinned in `stack-server.test.ts`; + * what is pinned HERE is that the export is gone and that nothing else went + * with it. + */ +describe('HttpServerConfig retirement (#4938)', () => { + // `HttpServerConfigSchema` and `HttpServerConfig` were both runtime VALUES + // (the latter via `Object.assign(HttpServerConfigSchema, { create })`), so a + // runtime `in` check is a real witness for them — reverse-verified by pasting + // the removed limb back, which turns these red plus the barrel assertion + // below. `HttpServerConfigInput` is type-only and cannot be seen from here at + // all: a `@ts-expect-error` pin would be a PHANTOM check, because this + // package's tsconfig excludes `**/*.test.ts` from `tsc --noEmit`. Its witness + // is `api-surface.json`, which lost all three entries in this change and is + // ratcheted by `check:api-surface` against the built `dist/*.d.ts`. + it.each([ + 'HttpServerConfigSchema', + 'HttpServerConfig', + ])('no longer exports the value `%s`', (name) => { + expect(name in httpServer).toBe(false); }); - it('should accept full configuration', () => { - const config = HttpServerConfigSchema.parse({ - port: 8080, - host: '127.0.0.1', - cors: { enabled: true, origins: ['http://localhost:3000'] }, - requestTimeout: 60000, - bodyLimit: '50mb', - compression: false, - security: { - helmet: false, - rateLimit: { windowMs: 60000, maxRequests: 100 }, - }, - trustProxy: true, - }); + it('does not re-export the removed shape from the system barrel either', async () => { + const system = await import('./index'); + expect('HttpServerConfigSchema' in system).toBe(false); + expect('HttpServerConfig' in system).toBe(false); + }); - expect(config.port).toBe(8080); - expect(config.host).toBe('127.0.0.1'); - expect(config.compression).toBe(false); - expect(config.trustProxy).toBe(true); + it('keeps the sibling exports that DO have consumers outside spec', () => { + // The removal is the container, not the file: `RouteHandlerMetadata` is + // consumed by `packages/rest/src/route-manager.ts` and `MiddlewareType` / + // `MiddlewareConfig` by `packages/runtime/src/middleware.ts`. A whole-file + // retirement would have broken both. + for (const name of [ + 'RouteHandlerMetadataSchema', + 'MiddlewareType', + 'MiddlewareConfigSchema', + 'MiddlewareConfig', + ]) { + expect(name in httpServer).toBe(true); + } }); - it('should reject invalid port numbers', () => { - expect(() => HttpServerConfigSchema.parse({ port: 0 })).toThrow(); - expect(() => HttpServerConfigSchema.parse({ port: 70000 })).toThrow(); - expect(() => HttpServerConfigSchema.parse({ port: -1 })).toThrow(); + it('leaves the shared value schemas it embedded in place — they are not orphaned', () => { + // Each has at least one live consumer elsewhere, so none of them became + // dead weight when their only `system/` embed point went away. + expect('CorsConfigSchema' in sharedHttp).toBe(true); + expect('StaticMountSchema' in sharedHttp).toBe(true); + expect('RateLimitConfigSchema' in sharedHttp).toBe(true); + + const routerShape = (RouterConfigSchema as never as { shape: Record }).shape; + expect(Object.keys(routerShape)).toEqual(expect.arrayContaining(['cors', 'staticMounts'])); + const endpointShape = (ApiEndpointSchema as never as { shape: Record }).shape; + expect(Object.keys(endpointShape)).toContain('rateLimit'); }); }); diff --git a/packages/spec/src/system/http-server.zod.ts b/packages/spec/src/system/http-server.zod.ts index f39d86a410..683b750705 100644 --- a/packages/spec/src/system/http-server.zod.ts +++ b/packages/spec/src/system/http-server.zod.ts @@ -1,14 +1,14 @@ // Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license. import { z } from 'zod'; -import { HttpMethod, CorsConfigSchema, RateLimitConfigSchema, StaticMountSchema } from '../shared/http.zod'; +import { HttpMethod } from '../shared/http.zod'; +import { lazySchema } from '../shared/lazy-schema'; /** * HTTP Server Protocol - * - * Defines the runtime HTTP server configuration and capabilities. - * Provides abstractions for HTTP server implementations (Express, Fastify, Hono, etc.) - * + * + * Route-registration metadata, middleware declaration and the server-side lifecycle/status vocabulary for HTTP server implementations (Express, Fastify, Hono, etc.) + * * Architecture alignment: * - Kubernetes: Service and Ingress resources * - AWS: API Gateway configuration @@ -16,74 +16,58 @@ import { HttpMethod, CorsConfigSchema, RateLimitConfigSchema, StaticMountSchema */ // ========================================== -// Server Configuration +// Server Configuration — RETIRED // ========================================== -/** - * HTTP Server Configuration Schema - * Core configuration for HTTP server instances - * - * @example - * { - * "port": 3000, - * "host": "0.0.0.0", - * "cors": { - * "enabled": true, - * "origins": ["http://localhost:3000"] - * }, - * "compression": true, - * "requestTimeout": 30000 - * } - */ -import { lazySchema } from '../shared/lazy-schema'; -export const HttpServerConfigSchema = lazySchema(() => z.object({ - /** - * Server port number - */ - port: z.number().int().min(1).max(65535).default(3000).describe('Port number to listen on'), - - /** - * Server host address - */ - host: z.string().default('0.0.0.0').describe('Host address to bind to'), - - /** - * CORS configuration - */ - cors: CorsConfigSchema.optional().describe('CORS configuration'), - - /** - * Request handling options - */ - requestTimeout: z.number().int().default(30000).describe('Request timeout in milliseconds'), - bodyLimit: z.string().default('10mb').describe('Maximum request body size'), - - /** - * Compression settings - */ - compression: z.boolean().default(true).describe('Enable response compression'), - - /** - * Security headers - */ - security: z.object({ - helmet: z.boolean().default(true).describe('Enable security headers via helmet'), - rateLimit: RateLimitConfigSchema.optional().describe('Global rate limiting configuration'), - }).optional().describe('Security configuration'), - - /** - * Static file serving - */ - static: z.array(StaticMountSchema).optional().describe('Static file serving configuration'), - - /** - * Trust proxy settings - */ - trustProxy: z.boolean().default(false).describe('Trust X-Forwarded-* headers'), -})); - -export type HttpServerConfig = z.infer; -export type HttpServerConfigInput = z.input; +// `HttpServerConfigSchema` / `HttpServerConfig` / `HttpServerConfigInput` and +// the `HttpServerConfig.create()` helper were REMOVED per ADR-0049 +// enforce-or-remove (#4938). The shape declared nine keys — `port`, `host`, +// `cors`, `requestTimeout`, `bodyLimit`, `compression`, `security`, `static`, +// `trustProxy` — and it was doubly inert: +// +// 1. ZERO runtime readers. No package in any repo (objectstack / cloud / +// objectui) ever parsed a document with this schema or read a key off it; +// the only non-spec mentions were "Used by:" comments in +// `shared/http.zod.ts` pointing back at it. +// 2. ZERO authoring entry — the condition that made this worse than the +// ordinary declared-but-unread defect. `stack.zod.ts` had no `server:` +// key, `config-schema.json` had no `HttpServerConfig`, and no settings +// manifest carried it, so the configuration `authorable-surface.json` +// listed and `content/docs/references/` rendered could not even be +// WRITTEN DOWN, let alone take effect. +// +// What actually decides these things, and where to configure each: +// +// | retired key | the live mechanism | +// |---|---| +// | `port` / `host` | the deployment, not the stack — `objectstack serve -p ` / `PORT` | +// | `static` | the transport plugin's `staticMounts` | +// | `cors` | the transport adapter — `OS_CORS_ORIGIN` / `OS_CORS_CREDENTIALS` / `OS_CORS_MAX_AGE` | +// | `security.helmet` | the dispatcher plugin's `securityHeaders` (on by default) | +// | `security.rateLimit` | `defineStack({ server: { security: { rateLimit } } })` — LIVE since #5006 | +// | `trustProxy` | `defineStack({ server: { trustProxy } })` — LIVE since #5006 | +// | `requestTimeout` / `bodyLimit` / `compression` | nothing. No seam consumes them; they return with an executor or not at all | +// +// Two of the nine were ACTIVATED rather than lost: #5006 mounted +// `security.rateLimit` and `trustProxy` on the deliberately narrow +// `StackServerConfigSchema` (`system/stack-server.zod.ts`), which is the one +// authoring surface for server-level configuration and grows one key at a time, +// each arriving with its consumer. That schema is `strictObject`, so the other +// seven keys are rejected BY NAME there with a per-key prescription — which is +// why this removal needs no `retiredKey()` tombstone: a tombstone is a message +// to whoever writes the key, and the only place anyone can write it already +// answers. Route 3 of the retirement playbook ("nothing parses it → neither"), +// the same shape #4834 / PR #4878 used for the kernel plugin-runtime family. +// +// `cors` is the one key with real business pull (`example-embed-objectql` +// proves embedding is a live scenario), and the 2026-08-04 ruling registered it +// as the FIRST per-key admission candidate into `server:` — to be admitted the +// #4910 way, WITH its executor, when the embedding work is scheduled. It is +// deliberately not parked on the export surface as a dead key in the meantime. +// +// `CorsConfigSchema`, `RateLimitConfigSchema` and `StaticMountSchema` stay in +// `shared/http.zod.ts`: each has other live consumers (`api/router.zod.ts`, +// `api/endpoint.zod.ts`, `system/stack-server.zod.ts`). // ========================================== // Route Registration @@ -352,12 +336,9 @@ export type ServerStatus = z.infer; // Helper Functions // ========================================== -/** - * Helper to create HTTP server configuration - */ -export const HttpServerConfig = Object.assign(HttpServerConfigSchema, { - create: >(config: T) => config, -}); +// The `HttpServerConfig` helper (`Object.assign(HttpServerConfigSchema, { +// create })`) went with the schema it wrapped — see the retirement note at the +// top of this file (#4938). /** * Helper to create middleware configuration diff --git a/packages/spec/src/system/stack-server.test.ts b/packages/spec/src/system/stack-server.test.ts index 86f78c5105..974b821abd 100644 --- a/packages/spec/src/system/stack-server.test.ts +++ b/packages/spec/src/system/stack-server.test.ts @@ -81,6 +81,12 @@ describe('server: carries only keys with a consumer (#4938 stays shut)', () => { const message = result.error!.issues.map((i) => i.message).join('\n'); expect(message).toMatch(/Unrecognized key/); expect(message).toMatch(expected); + // Since #4938 these seven are not merely "not mounted here" — the shape + // that declared them is GONE, and this strict guidance map is the only + // surface left that answers for them. That is why the retirement needed no + // `retiredKey()` tombstone; if this assertion ever stops holding, the + // prescription has drifted away from the removal it stands in for. + expect(message).toMatch(/retired in v17/); }); }); diff --git a/packages/spec/src/system/stack-server.zod.ts b/packages/spec/src/system/stack-server.zod.ts index 0aefc53452..9623b7c687 100644 --- a/packages/spec/src/system/stack-server.zod.ts +++ b/packages/spec/src/system/stack-server.zod.ts @@ -5,8 +5,8 @@ * * ## Why this is NOT `HttpServerConfigSchema` * - * `system/http-server.zod.ts` declares nine keys (`port`, `host`, `cors`, - * `requestTimeout`, `bodyLimit`, `compression`, `security`, `static`, + * `system/http-server.zod.ts` used to declare nine keys (`port`, `host`, + * `cors`, `requestTimeout`, `bodyLimit`, `compression`, `security`, `static`, * `trustProxy`). #4938 measured them: **none had a runtime reader and none was * reachable from any authoring surface** — `stack.zod.ts` had no `server:` key, * so the whole shape was unwritable as well as unread. Mounting it wholesale @@ -22,9 +22,19 @@ * | `security.rateLimit` | `createDispatcherPlugin` → the inbound token bucket (`@objectstack/runtime` `security/inbound-rate-limit.ts`) — an over-budget caller gets `429` + `Retry-After` | * | `trustProxy` | the same limiter's IP resolution — see below | * - * The other seven `HttpServerConfigSchema` keys stay unreachable, and their - * enforce-or-remove fate is tracked by #4938. Adding one here without an - * executor re-opens the hole this narrowness exists to close. + * The other seven `HttpServerConfigSchema` keys were RETIRED with the shape + * that carried them (#4938, ADR-0049 enforce-or-remove): unreachable *and* + * unread, they were the cleanest remove candidate in the ledger, and their + * prescriptions now live in the `guidance` maps below — the only place an + * author can write a server key is also the only place that has to answer for + * one. Adding one here without an executor re-opens the hole this narrowness + * exists to close. + * + * `cors` is the registered exception-in-waiting: the 2026-08-04 ruling named it + * the FIRST per-key admission candidate for this shape, because embedding + * (`example-embed-objectql`) is a real scenario with real pull. When that work + * is scheduled it arrives the #4910 way — key and executor in one change — not + * by un-retiring a declaration. * * ## What `server:` is NOT for * @@ -170,17 +180,27 @@ export const StackServerConfigSchema = lazySchema(() => strictObject( guidance: { port: 'Not authorable. The listening port belongs to the deployment, not the stack — pass ' - + '`objectstack serve -p ` or set PORT.', + + '`objectstack serve -p ` or set PORT. (`HttpServerConfig.port` was retired in v17, #4938.)', host: 'Not authorable. The bind address belongs to the deployment, not the stack — pass it to ' - + '`objectstack serve`.', + + '`objectstack serve`. (`HttpServerConfig.host` was retired in v17, #4938.)', cors: 'Not authorable here. CORS is owned by the transport adapter and configured by ' - + 'OS_CORS_ORIGIN / OS_CORS_CREDENTIALS / OS_CORS_MAX_AGE.', - compression: 'Not authorable — no runtime reads it (#4938).', - requestTimeout: 'Not authorable — no runtime reads it (#4938).', - bodyLimit: 'Not authorable — no runtime reads it (#4938).', - static: 'Not authorable. Static mounts are configured on the transport plugin (`staticMounts`).', + + 'OS_CORS_ORIGIN / OS_CORS_CREDENTIALS / OS_CORS_MAX_AGE. (`HttpServerConfig.cors` was retired in ' + + 'v17, #4938; it is the registered first candidate for a `server.cors` key, which will arrive with ' + + 'its executor.)', + compression: + 'Not authorable — nothing reads it. `HttpServerConfig.compression` was retired in v17 (#4938) ' + + 'rather than mounted here; response compression is the transport adapter\'s concern.', + requestTimeout: + 'Not authorable — nothing reads it. `HttpServerConfig.requestTimeout` was retired in v17 (#4938) ' + + 'rather than mounted here.', + bodyLimit: + 'Not authorable — nothing reads it. `HttpServerConfig.bodyLimit` was retired in v17 (#4938) ' + + 'rather than mounted here.', + static: + 'Not authorable. Static mounts are configured on the transport plugin (`staticMounts`). ' + + '(`HttpServerConfig.static` was retired in v17, #4938.)', }, }, { From 36ab4bb8204b07356e9fb3696c1ff61f824476f2 Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 17:56:16 +0000 Subject: [PATCH 2/2] =?UTF-8?q?chore(spec):=20=E4=B8=8E=20main=20=E5=90=8C?= =?UTF-8?q?=E6=AD=A5=E5=90=8E=E6=95=B4=E4=BD=93=E9=87=8D=E7=94=9F=E6=88=90?= =?UTF-8?q?=E7=94=9F=E6=88=90=E7=89=A9(#5289=20=E8=90=BD=E5=9C=B0=E5=90=8E?= =?UTF-8?q?)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `git merge origin/main` 零冲突,但 os-regen 驱动在生成物上不做文本合并, 所以按流程把 8 条 os-regen 路径整体 checkout 回 origin/main,再全量重跑 生成器(gen:schema / gen:api-surface / gen:spec-changes / gen:upgrade-guide / gen:docs / gen:skill-refs / gen:skill-docs / gen:strictness-ledger),两侧条目 逐条断言仍在。 - 兄弟侧 #5289:6 条 `ui/Theme` / `ui/Typography` 的 `[RETIRED]` 标记在册; `ui/Animation` / `ui/ZIndex` 两个 def 仍不在 manifest; `theme-inert-token-scales-removed` 的 D2 条目与 D3 链步完好,并已到达 `spec-changes.json` 与 protocol-upgrade-guide。 - 本侧 #4938:manifest −1 / authorable −9 / api-surface −3 仍生效; 两处「有意删除」在新基线 f8cfbb4 上按 #2978 与 #4650 路径 3 重新自证。 - strictness ledger 整体重跑(未手改数字):`system/` 368 → 366,triaged 总数 476 由 #5289 带入,非本 PR 改动。 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01ErbEDVAg1No9gdg1pgDAGB --- ...07-unknown-key-strictness-ledger.counts.md | 14 +++++------ packages/spec/api-surface.json | 4 ---- packages/spec/authorable-surface.json | 24 ++++++------------- packages/spec/json-schema.manifest.json | 4 +--- 4 files changed, 15 insertions(+), 31 deletions(-) diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md index 156d45ec6a..72f2ba3c47 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md @@ -21,7 +21,7 @@ regenerate. | Measure | Value | |---|---| | Triaged directories | 5 | -| Object sites in them | 484 | +| Object sites in them | 476 | | Still-open (strip) sites | 221 | | Files carrying at least one | 36 | @@ -43,12 +43,12 @@ The `strict` column is the one the campaign schedules against; it counts both th | Dir | Sites | strict | passthrough | catchall | strip | |---|---|---|---|---|---| -| `ui/` | 200 | 120 | 5 | 0 | 75 | +| `ui/` | 192 | 112 | 5 | 0 | 75 | | `data/` | 162 | 54 | 1 | 0 | 107 | | `automation/` | 75 | 49 | 0 | 0 | 26 | | `security/` | 20 | 7 | 0 | 0 | 13 | | `studio/` | 27 | 27 | 0 | 0 | 0 | -| **total** | **484** | **257** | **6** | **0** | **221** | +| **total** | **476** | **249** | **6** | **0** | **221** | ## File-level triage — site counts @@ -77,11 +77,11 @@ classify and is not listed (it becomes reportable the day it grows its first sit | `report.zod.ts` | 3 | | `responsive.zod.ts` | 4 | | `sharing.zod.ts` | 2 | -| `theme.zod.ts` | 14 | +| `theme.zod.ts` | 6 | | `touch.zod.ts` | 7 | | `view.zod.ts` | 51 | | `widget.zod.ts` | 9 | -| **total** | **200** | +| **total** | **192** | ### `data/` — sites @@ -161,7 +161,7 @@ over it is here. ### `ui/` — open -**75 strip of 200**, in 13 file(s). +**75 strip of 192**, in 13 file(s). | File | Strip | Sites | |---|---|---| @@ -178,7 +178,7 @@ over it is here. | `touch.zod.ts` | 7 | 7 | | `view.zod.ts` | 5 | 51 | | `widget.zod.ts` | 9 | 9 | -| **total** | **75** | **200** | +| **total** | **75** | **192** | | Bucket | Sites | |---|---| diff --git a/packages/spec/api-surface.json b/packages/spec/api-surface.json index b6c05a69d3..75e3d8ca7c 100644 --- a/packages/spec/api-surface.json +++ b/packages/spec/api-surface.json @@ -3152,8 +3152,6 @@ "ActionType (const)", "AddRecordConfig (type)", "AddRecordConfigSchema (const)", - "Animation (type)", - "AnimationSchema (const)", "AnimationTrigger (type)", "AnimationTriggerSchema (const)", "App (type)", @@ -3517,8 +3515,6 @@ "WidgetPropertySchema (const)", "WidgetSource (type)", "WidgetSourceSchema (const)", - "ZIndex (type)", - "ZIndexSchema (const)", "actionForm (const)", "appForm (const)", "chartAggregateCategoryKey (function)", diff --git a/packages/spec/authorable-surface.json b/packages/spec/authorable-surface.json index 36dc0febfe..cfde5a17ef 100644 --- a/packages/spec/authorable-surface.json +++ b/packages/spec/authorable-surface.json @@ -6914,8 +6914,6 @@ "ui/AddRecordConfig:formView", "ui/AddRecordConfig:mode", "ui/AddRecordConfig:position", - "ui/Animation:duration", - "ui/Animation:timing", "ui/App:_lock", "ui/App:_lockDocsUrl", "ui/App:_lockReason", @@ -7885,7 +7883,7 @@ "ui/SyncConfig:maxRetries", "ui/SyncConfig:retryInterval", "ui/SyncConfig:strategy", - "ui/Theme:animation", + "ui/Theme:animation [RETIRED]", "ui/Theme:borderRadius", "ui/Theme:colors", "ui/Theme:customVars", @@ -7896,7 +7894,7 @@ "ui/Theme:name", "ui/Theme:shadows", "ui/Theme:typography", - "ui/Theme:zIndex", + "ui/Theme:zIndex [RETIRED]", "ui/TimelineConfig:colorField", "ui/TimelineConfig:endDateField", "ui/TimelineConfig:groupByField", @@ -7924,10 +7922,10 @@ "ui/TreeConfig:labelField", "ui/TreeConfig:parentField", "ui/Typography:fontFamily", - "ui/Typography:fontSize", - "ui/Typography:fontWeight", - "ui/Typography:letterSpacing", - "ui/Typography:lineHeight", + "ui/Typography:fontSize [RETIRED]", + "ui/Typography:fontWeight [RETIRED]", + "ui/Typography:letterSpacing [RETIRED]", + "ui/Typography:lineHeight [RETIRED]", "ui/UrlNavItem:badge", "ui/UrlNavItem:badgeVariant", "ui/UrlNavItem:icon", @@ -8028,14 +8026,6 @@ "ui/WidgetProperty:name", "ui/WidgetProperty:required", "ui/WidgetProperty:type", - "ui/WidgetProperty:validation", - "ui/ZIndex:base", - "ui/ZIndex:dropdown", - "ui/ZIndex:fixed", - "ui/ZIndex:modal", - "ui/ZIndex:modalBackdrop", - "ui/ZIndex:popover", - "ui/ZIndex:sticky", - "ui/ZIndex:tooltip" + "ui/WidgetProperty:validation" ] } diff --git a/packages/spec/json-schema.manifest.json b/packages/spec/json-schema.manifest.json index 102175edcd..3a36673ca7 100644 --- a/packages/spec/json-schema.manifest.json +++ b/packages/spec/json-schema.manifest.json @@ -1460,7 +1460,6 @@ "ui/ActionParam", "ui/ActionType", "ui/AddRecordConfig", - "ui/Animation", "ui/AnimationTrigger", "ui/App", "ui/AppBranding", @@ -1639,7 +1638,6 @@ "ui/WidgetLifecycle", "ui/WidgetManifest", "ui/WidgetProperty", - "ui/WidgetSource", - "ui/ZIndex" + "ui/WidgetSource" ] }