diff --git a/.changeset/rest-api-endpoint-handler-status-retired.md b/.changeset/rest-api-endpoint-handler-status-retired.md new file mode 100644 index 0000000000..f5ea9b8327 --- /dev/null +++ b/.changeset/rest-api-endpoint-handler-status-retired.md @@ -0,0 +1,91 @@ +--- +"@objectstack/spec": minor +--- + +feat(spec): retire `RestApiEndpoint.handlerStatus` and the Route Coverage Report shapes — declared, never read; the 501 they described comes from the endpoint executor (#13823, ADR-0049) + + + +**BREAKING** accept-set narrowing and export removal, landing after the +v17.0.0 cut (the lockstep launch-window convention ships it as `minor`; the +prescription is registered under protocol major 18 — +`RETIRED_KEYS_BY_MAJOR[18]` for the key, `RETIRED_DEFS_BY_MAJOR[18]` for the +three defs, plus the D3 semantic entry +`rest-api-endpoint-handler-status-retired` — where `os migrate meta` users +will look). Maintainer ruling 2026-09-01 on #13823 (director decision batch +#27, verbatim 「同意」): remove; enforce excluded. + +`handlerStatus` (`implemented` / `stub` / `planned`) was an authorable key on +`RestApiEndpointSchema` whose docstring promised that a `stub` handler +"returns 501 Not Implemented", and **nothing read it**. Measured at the +retirement base (`origin/main` a9b2be0b0, 2026-09-02, `skills/**` and tests +excluded): the only identifier hits were the declaration, its re-declaration +on `RouteCoverageEntrySchema` and a docblock saying adapters SHOULD warn on +it. The 501 it described has a different cause — every +`DispatcherErrorCode.enum.NOT_IMPLEMENTED` site (`runtime/src/endpoint-executor.ts` +×3, `runtime/src/api-mapping.ts`, `runtime/src/api-endpoint-step.ts`) is the +declarative-endpoint executor refusing a target or mapping it cannot serve, +and none consults the key. So an author who wrote `handlerStatus: 'stub'` +expecting a 501 got an ordinarily served route, and the declaration reported +progress to nobody: `RouteCoverageReportSchema`, the only shape that would +have carried it outward, had zero constructors in objectstack, objectui +(pinned sha) and cloud. + +FROM → TO: + +- `handlerStatus: 'implemented' | 'stub' | 'planned'` on a `RestApiEndpoint` + → *(removed key)* — tombstoned with `retiredKey()` (the schema is not + `.strict()`, so a bare deletion would be a silent strip): authoring it is + now a `tsc` error and a parse error carrying the prescription at path + `handlerStatus`, for every former value including the documented default + `'implemented'` (prose only — the key never carried a Zod `.default()`, so + no built artifact materialised it and there is no residue window). +- `HandlerStatusSchema` / `HandlerStatus` → *(removed — no replacement)*. The + enum's only two carriers leave in this same change; an exported value + schema with no consumer reads as a capability (#3950). +- `RouteCoverageEntrySchema` / `RouteCoverageEntry` and + `RouteCoverageReportSchema` / `RouteCoverageReport` → *(removed — no + replacement)*. No adapter, dispatcher or registrar ever constructed the + report; it was a shape with no producer. + +One-line fix: delete the key — nothing served changes, because nothing ever +read it. An endpoint that has no handler yet is simply not registered. Route +readiness that IS measured is untouched: the discovery payload's per-service +`status` / `handlerReady` (`api/discovery.zod.ts`) and the CI-asserted route +ledger (`packages/runtime/src/route-ledger.ts`). A declared-but-unbuilt route +answering 501 instead of 404 is a new capability the ruling explicitly +excluded (zero pull); if it is ever wanted it re-declares fresh under its own +ruling, executor first. + +The retirement kit: + +- key tombstone at the declaration (`api/RestApiEndpoint:handlerStatus` in + `RETIRED_KEYS_BY_MAJOR[18]`; the surface baseline line carries `[RETIRED]`) +- whole-def deletions `api/HandlerStatus`, `api/RouteCoverageEntry`, + `api/RouteCoverageReport` in `RETIRED_DEFS_BY_MAJOR[18]` (manifest keys + deliberately removed; the #4725 gate adjudicated them) +- deliberately NO D2 conversion: nothing in the tree parses + `RestApiEndpointSchema` outside its own unit tests — a REST API plugin route + registration is not a stack collection member and never a `sys_metadata` + row — so the conversion chain has no seam that would ever see one (the + `kernel/Manifest:loading` disposition); the D3 semantic entry carries the + prescription, and for the same reason the tombstone carries no + `os migrate meta` sentence +- pin tests (`api/plugin-rest-api.handler-status-retirement.test.ts`): all + three former values refused at path `handlerStatus` with the prescription, + through the route-registration embed too; a well-formed endpoint without the + key still parses and grows no `handlerStatus` property; the shipped default + route registrations still parse; zero holders for all 6 retired export names + on every public entry; the carrier schemas survive; the registrations under + major 18 are present +- no liveness-ledger row moves: `RestApiEndpointSchema` is outside the walked + population (not a registered metadata type and not in `SPEC_ONLY_SCHEMAS`), + so the verdict is recorded here, in the schema and in the D3 entry +- teaching sweep: `content/docs/**`, `examples/**`, `packages/*/README.md` and + `packages/create-objectstack/**` carry no hand-written mention; the generated + reference page regenerates. The published skill `skills/objectstack-api` + taught the key at this PR's base (a9b2be0b0); `main` already dropped that + teaching in the #14448 optimization flight (bae4088f3 — zero `handlerStatus` + / Handler Status / RouteCoverage hits under `skills/` at 99b8faf8f, with + `RestApiEndpoint` still hitting the skill as the live control), so no + skills-lane follow-up is needed diff --git a/content/docs/references/api/plugin-rest-api.mdx b/content/docs/references/api/plugin-rest-api.mdx index 231b503bac..ac699564d0 100644 --- a/content/docs/references/api/plugin-rest-api.mdx +++ b/content/docs/references/api/plugin-rest-api.mdx @@ -60,8 +60,8 @@ no plugin code at all. ## TypeScript Usage ```typescript -import { ErrorHandlingConfigSchema, HandlerStatusSchema, OpenApiGenerationConfigSchema, RequestValidationConfigSchema, ResponseEnvelopeConfigSchema, RestApiEndpointSchema, RestApiPluginConfigSchema, RestApiRouteCategory, RestApiRouteRegistrationSchema, RouteCoverageEntrySchema, RouteCoverageReportSchema, ValidationMode } from '@objectstack/spec/api'; -import type { ErrorHandlingConfig, HandlerStatus, OpenApiGenerationConfig, RequestValidationConfig, ResponseEnvelopeConfig, RestApiEndpoint, RestApiPluginConfig, RestApiRouteCategory, RestApiRouteRegistration, RouteCoverageEntry, RouteCoverageReport, ValidationMode } from '@objectstack/spec/api'; +import { ErrorHandlingConfigSchema, OpenApiGenerationConfigSchema, RequestValidationConfigSchema, ResponseEnvelopeConfigSchema, RestApiEndpointSchema, RestApiPluginConfigSchema, RestApiRouteCategory, RestApiRouteRegistrationSchema, ValidationMode } from '@objectstack/spec/api'; +import type { ErrorHandlingConfig, OpenApiGenerationConfig, RequestValidationConfig, ResponseEnvelopeConfig, RestApiEndpoint, RestApiPluginConfig, RestApiRouteCategory, RestApiRouteRegistration, ValidationMode } from '@objectstack/spec/api'; // Validate data const result = ErrorHandlingConfigSchema.parse(data); @@ -87,17 +87,6 @@ const result = ErrorHandlingConfigSchema.parse(data); | **redactFields** | `string[]` | optional | Field names to redact from error details | ---- - -## HandlerStatus - -### Allowed Values - -* `implemented` -* `stub` -* `planned` - - --- ## OpenApiGenerationConfig @@ -197,7 +186,7 @@ const result = ErrorHandlingConfigSchema.parse(data); | **rateLimit** | `string` | optional | Rate limit policy name | | **cacheable** | `boolean` | optional (default: `false`) | Whether response can be cached | | **cacheTtl** | `integer` | optional | Cache TTL in seconds | -| **handlerStatus** | `Enum<'implemented' \| 'stub' \| 'planned'>` | optional | Handler implementation status: implemented (default if omitted), stub, or planned | +| **handlerStatus** | `never` | optional | [REMOVED] `RestApiEndpoint.handlerStatus` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no registrar, dispatcher or adapter consulted the key, so an endpoint declared `stub` or `planned` was served exactly like an `implemented` one, and the `501 NOT_IMPLEMENTED` its docstring promised is raised by the declarative-endpoint executor for a target it cannot serve, never from this field. Delete the key. An endpoint that has no handler yet is simply not registered; a declared-but-unbuilt route answering 501 is not a platform capability (ruling record, 2026-09-01). | --- @@ -372,7 +361,7 @@ const result = ErrorHandlingConfigSchema.parse(data); | **rateLimit** | `string` | optional | Rate limit policy name | | **cacheable** | `boolean` | optional (default: `false`) | Whether response can be cached | | **cacheTtl** | `integer` | optional | Cache TTL in seconds | -| **handlerStatus** | `Enum<'implemented' \| 'stub' \| 'planned'>` | optional | Handler implementation status: implemented (default if omitted), stub, or planned | +| **handlerStatus** | `never` | optional | [REMOVED] `RestApiEndpoint.handlerStatus` was removed in @objectstack/spec 17 (ADR-0049 enforce-or-remove) — nothing ever read it: no registrar, dispatcher or adapter consulted the key, so an endpoint declared `stub` or `planned` was served exactly like an `implemented` one, and the `501 NOT_IMPLEMENTED` its docstring promised is raised by the declarative-endpoint executor for a target it cannot serve, never from this field. Delete the key. An endpoint that has no handler yet is simply not registered; a declared-but-unbuilt route answering 501 is not a platform capability (ruling record, 2026-09-01). | ### Nested Shape: `RestApiRouteRegistration.middleware[number]` @@ -394,56 +383,6 @@ const result = ErrorHandlingConfigSchema.parse(data); | **tags** | `string[]` | optional | OpenAPI tags | ---- - -## RouteCoverageEntry - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **path** | `string` | ✅ | Full URL path (e.g. /api/v1/analytics/query) | -| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` | ✅ | HTTP method (GET, POST, etc.) | -| **category** | `Enum<'discovery' \| 'metadata' \| 'data' \| 'batch' \| 'permission' \| 'analytics' \| 'automation' \| 'ui' \| 'realtime' \| 'notification' \| 'ai' \| 'i18n'>` | ✅ | Route category | -| **handlerStatus** | `Enum<'implemented' \| 'stub' \| 'planned'>` | ✅ | Handler status | -| **service** | `string` | ✅ | Target service name | -| **healthCheckPassed** | `boolean` | optional | Whether the health check probe succeeded | - - ---- - -## RouteCoverageReport - -### Properties - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **timestamp** | `string` | ✅ | ISO 8601 timestamp | -| **adapter** | `string` | ✅ | Adapter name (e.g. "hono", "express", "nextjs") | -| **summary** | `{ total: integer; implemented: integer; stub: integer; planned: integer }` | ✅ | | -| **entries** | `{ path: string; method: Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>; category: Enum<'discovery' \| 'metadata' \| 'data' \| 'batch' \| 'permission' \| 'analytics' \| …>; handlerStatus: Enum<'implemented' \| 'stub' \| 'planned'>; … }[]` | ✅ | Per-endpoint coverage entries | - -### Nested Shape: `RouteCoverageReport.summary` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **total** | `integer` | ✅ | Total declared endpoints | -| **implemented** | `integer` | ✅ | Endpoints with real handlers | -| **stub** | `integer` | ✅ | Endpoints with stub handlers (501) | -| **planned** | `integer` | ✅ | Endpoints not yet implemented | - -### Nested Shape: `RouteCoverageReport.entries[number]` - -| Property | Type | Required | Description | -| :--- | :--- | :--- | :--- | -| **path** | `string` | ✅ | Full URL path (e.g. /api/v1/analytics/query) | -| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` | ✅ | HTTP method (GET, POST, etc.) | -| **category** | `Enum<'discovery' \| 'metadata' \| 'data' \| 'batch' \| 'permission' \| 'analytics' \| …>` | ✅ | Route category | -| **handlerStatus** | `Enum<'implemented' \| 'stub' \| 'planned'>` | ✅ | Handler status | -| **service** | `string` | ✅ | Target service name | -| **healthCheckPassed** | `boolean` | optional | Whether the health check probe succeeded | - - --- ## ValidationMode diff --git a/content/docs/references/index.mdx b/content/docs/references/index.mdx index 71c4c2fd4f..4761aa1d2b 100644 --- a/content/docs/references/index.mdx +++ b/content/docs/references/index.mdx @@ -1,6 +1,6 @@ --- title: Protocol Reference -description: Every schema published by @objectstack/spec — 1593 schemas across 14 protocol modules +description: Every schema published by @objectstack/spec — 1590 schemas across 14 protocol modules --- {/* ⚠️ AUTO-GENERATED — DO NOT EDIT. Run build-docs.ts to regenerate. Hand-written docs live in the module folders under content/docs/. */} @@ -20,7 +20,7 @@ counts are sums of the rows they head. Regenerate with | Module | Pages | Schemas | Description | | :--- | ---: | ---: | :--- | | [AI Protocol](/docs/references/ai) | 11 | 66 | Agents, tools, skills, RAG and knowledge sources, model registry, conversations. | -| [API Protocol](/docs/references/api) | 31 | 440 | REST contracts, endpoints, routing, realtime, batch, discovery. | +| [API Protocol](/docs/references/api) | 31 | 437 | REST contracts, endpoints, routing, realtime, batch, discovery. | | [Automation Protocol](/docs/references/automation) | 13 | 69 | Flows and their nodes, approvals, ETL pipelines, webhooks, state machines, execution records. | | [Cloud Protocol](/docs/references/cloud) | 11 | 94 | Environments, packages and versions, marketplace, developer portal, tenancy. | | [Data Protocol](/docs/references/data) | 29 | 166 | Objects, fields, queries, filters, datasources and drivers — the ObjectQL layer. | @@ -33,7 +33,7 @@ counts are sums of the rows they head. Regenerate with | [Studio Protocol](/docs/references/studio) | 3 | 35 | Studio designer metadata — the authoring surfaces for the protocols above. | | [System Protocol](/docs/references/system) | 36 | 291 | The runtime environment — logging, jobs, cache, metrics, notifications, i18n and compliance. | | [UI Protocol](/docs/references/ui) | 16 | 153 | Apps, pages, views, dashboards, reports, actions and themes — the ObjectUI layer. | -| **Total** | **199** | **1593** | 14 protocol modules | +| **Total** | **199** | **1590** | 14 protocol modules | --- @@ -61,7 +61,7 @@ Agents, tools, skills, RAG and knowledge sources, model registry, conversations. ## API Protocol -**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **31 pages, 440 schemas** +**Source:** `packages/spec/src/api/` · **Import:** `@objectstack/spec/api` · **31 pages, 437 schemas** REST contracts, endpoints, routing, realtime, batch, discovery. @@ -87,7 +87,7 @@ REST contracts, endpoints, routing, realtime, batch, discovery. | [`odata.zod.ts`](/docs/references/api/odata) | `ODataConfig`, `ODataError`, `ODataFilterFunction`, `ODataMetadata`, `ODataQuery`, `ODataResponse` | | [`package-api.zod.ts`](/docs/references/api/package-api) | `GetInstalledPackageRequest`, `GetInstalledPackageResponse`, `ListInstalledPackagesRequest`, `ListInstalledPackagesResponse`, `PackageApiErrorCode`, `PackageInstallRequest`, `PackageInstallResponse`, `PackagePathParams`, `PackageRollbackRequest`, `PackageUpgradeRequest`, `PackageUpgradeResponse`, `ResolveDependenciesRequest`, `ResolveDependenciesResponse`, `UninstallPackageApiRequest`, `UninstallPackageApiResponse`, `UploadArtifactRequest`, `UploadArtifactResponse` | | [`package-lifecycle.zod.ts`](/docs/references/api/package-lifecycle) | `DiscardPackageDraftsResponse`, `DuplicatePackageResponse`, `ListPackageCommitsResponse`, `PackageExportManifest`, `PackagePublishResult`, `ReassignOrphanedMetadataResponse`, `RevertPackageCommitResponse`, `RollbackToPackageCommitResponse` | -| [`plugin-rest-api.zod.ts`](/docs/references/api/plugin-rest-api) | `ErrorHandlingConfig`, `HandlerStatus`, `OpenApiGenerationConfig`, `RequestValidationConfig`, `ResponseEnvelopeConfig`, `RestApiEndpoint`, `RestApiPluginConfig`, `RestApiRouteCategory`, `RestApiRouteRegistration`, `RouteCoverageEntry`, `RouteCoverageReport`, `ValidationMode` | +| [`plugin-rest-api.zod.ts`](/docs/references/api/plugin-rest-api) | `ErrorHandlingConfig`, `OpenApiGenerationConfig`, `RequestValidationConfig`, `ResponseEnvelopeConfig`, `RestApiEndpoint`, `RestApiPluginConfig`, `RestApiRouteCategory`, `RestApiRouteRegistration`, `ValidationMode` | | [`protocol.zod.ts`](/docs/references/api/protocol) | `AiAgentCapabilities`, `AiAgentChatRequest`, `AiAgentSummary`, `AiAgentsResponse`, `AiChatRequest`, `AiChatResponse`, `AiCompleteRequest`, `AiConversation`, `AiMessage`, `AiModelsResponse`, `AiPendingAction`, `AiPendingActionStatus`, `AiStreamChunk`, `ApproveAiPendingActionResponse`, `AuditMetaItemRequest`, `AuditMetaItemResponse`, `AutomationActionsResponse`, `AutomationTriggerRequest`, `AutomationTriggerResponse`, `BatchDataRequest`, `BatchDataResponse`, `CheckPermissionRequest`, `CheckPermissionResponse`, `CloneDataResponse`, `CreateAiConversationRequest`, `CreateDataRequest`, `CreateDataResponse`, `CreateManyDataRequest`, `CreateManyDataResponse`, `DeleteDataRequest`, `DeleteDataResponse`, `DeleteManyDataRequest`, `DeleteManyDataResponse`, `DeleteMetaItemRequest`, `DeleteMetaItemResponse`, `DiffMetaItemResponse`, `DisablePackageRequest`, `DisablePackageResponse`, `EnablePackageRequest`, `EnablePackageResponse`, `FindDataRequest`, `FindDataResponse`, `FindReferencesToMetaResponse`, `GetDataRequest`, `GetDataResponse`, `GetDiscoveryRequest`, `GetDiscoveryResponse`, `GetEffectivePermissionsRequest`, `GetEffectivePermissionsResponse`, `GetFieldLabelsRequest`, `GetFieldLabelsResponse`, `GetLocalesRequest`, `GetLocalesResponse`, `GetMetaDiagnosticsResponse`, `GetMetaItemCachedRequest`, `GetMetaItemCachedResponse`, `GetMetaItemLayeredRequest`, `GetMetaItemLayeredResponse`, `GetMetaItemRequest`, `GetMetaItemResponse`, `GetMetaItemsRequest`, `GetMetaItemsResponse`, `GetMetaTypesRequest`, `GetMetaTypesResponse`, `GetNotificationPreferencesRequest`, `GetNotificationPreferencesResponse`, `GetObjectPermissionsRequest`, `GetObjectPermissionsResponse`, `GetPackageRequest`, `GetPackageResponse`, `GetPresenceRequest`, `GetPresenceResponse`, `GetPublishedMetaItemResponse`, `GetTranslationsRequest`, `GetTranslationsResponse`, `GetUiViewRequest`, `GetUiViewResponse`, `HistoryMetaItemRequest`, `HistoryMetaItemResponse`, `HttpFindQueryParams`, `InstallPackageRequest`, `InstallPackageResponse`, `ListAiConversationsRequest`, `ListAiConversationsResponse`, `ListAiPendingActionsRequest`, `ListAiPendingActionsResponse`, `ListDraftsResponse`, `ListNotificationsRequest`, `ListNotificationsResponse`, `ListPackagesRequest`, `ListPackagesResponse`, `MarkAllNotificationsReadRequest`, `MarkAllNotificationsReadResponse`, `MarkNotificationsReadRequest`, `MarkNotificationsReadResponse`, `Notification`, `NotificationPreferences`, `PublishMetaItemRequest`, `PublishMetaItemResponse`, `PublishPackageDraftsResponse`, `RealtimeConnectRequest`, `RealtimeConnectResponse`, `RealtimeDisconnectRequest`, `RealtimeDisconnectResponse`, `RealtimeSubscribeRequest`, `RealtimeSubscribeResponse`, `RealtimeUnsubscribeRequest`, `RealtimeUnsubscribeResponse`, `RegisterDeviceRequest`, `RegisterDeviceResponse`, `RejectAiPendingActionResponse`, `RollbackMetaItemResponse`, `RuntimeAuthoringIssue`, `SaveMetaItemRequest`, `SaveMetaItemResponse`, `SearchAllHit`, `SearchAllPageHit`, `SearchAllResponse`, `SetPresenceRequest`, `SetPresenceResponse`, `UninstallPackageRequest`, `UninstallPackageResponse`, `UnregisterDeviceRequest`, `UnregisterDeviceResponse`, `UpdateAiConversationRequest`, `UpdateDataRequest`, `UpdateDataResponse`, `UpdateManyDataRequest`, `UpdateManyDataResponse`, `UpdateNotificationPreferencesRequest`, `UpdateNotificationPreferencesResponse`, `ValidateDataIssue`, `ValidateDataRequest`, `ValidateDataResponse` | | [`query-adapter.zod.ts`](/docs/references/api/query-adapter) | `ODataQueryAdapter`, `OperatorMapping`, `QueryAdapterConfig`, `QueryAdapterTarget`, `RestQueryAdapter` | | [`realtime.zod.ts`](/docs/references/api/realtime) | `RealtimeConfig`, `RealtimeEvent`, `RealtimeEventType`, `RealtimePresence`, `Subscription`, `SubscriptionEvent`, `TransportProtocol` | 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 6c56654d9f..f3ed147475 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.counts.md @@ -257,7 +257,7 @@ directory rather than per file. | Dir | Sites | |---|---| | `ai/` | 77 | -| `api/` | 455 | +| `api/` | 452 | | `cloud/` | 83 | | `identity/` | 32 | | `integration/` | 10 | diff --git a/packages/spec/api-surface/api.json b/packages/spec/api-surface/api.json index 80daddcdc4..b538837362 100644 --- a/packages/spec/api-surface/api.json +++ b/packages/spec/api-surface/api.json @@ -495,8 +495,6 @@ "GetUiViewResponse (type)", "GetUiViewResponseParsed (type)", "GetUiViewResponseSchema (const)", - "HandlerStatus (type)", - "HandlerStatusSchema (const)", "HistoryMetaItemRequest (type)", "HistoryMetaItemRequestSchema (const)", "HistoryMetaItemResponse (type)", @@ -878,10 +876,6 @@ "RollbackToPackageCommitResponseParsed (type)", "RollbackToPackageCommitResponseSchema (const)", "RouteCategory (type)", - "RouteCoverageEntry (type)", - "RouteCoverageEntrySchema (const)", - "RouteCoverageReport (type)", - "RouteCoverageReportSchema (const)", "RouteDefinition (type)", "RouteDefinitionParsed (type)", "RouteDefinitionSchema (const)", diff --git a/packages/spec/authorable-surface/api.json b/packages/spec/authorable-surface/api.json index 93cf71ee0a..bc5a9b3c55 100644 --- a/packages/spec/authorable-surface/api.json +++ b/packages/spec/authorable-surface/api.json @@ -1456,7 +1456,7 @@ "api/RestApiEndpoint:category", "api/RestApiEndpoint:description", "api/RestApiEndpoint:handler", - "api/RestApiEndpoint:handlerStatus", + "api/RestApiEndpoint:handlerStatus [RETIRED]", "api/RestApiEndpoint:method", "api/RestApiEndpoint:path", "api/RestApiEndpoint:permissions", @@ -1510,16 +1510,6 @@ "api/RollbackToPackageCommitResponse:failed", "api/RollbackToPackageCommitResponse:revertedCommits", "api/RollbackToPackageCommitResponse:success", - "api/RouteCoverageEntry:category", - "api/RouteCoverageEntry:handlerStatus", - "api/RouteCoverageEntry:healthCheckPassed", - "api/RouteCoverageEntry:method", - "api/RouteCoverageEntry:path", - "api/RouteCoverageEntry:service", - "api/RouteCoverageReport:adapter", - "api/RouteCoverageReport:entries", - "api/RouteCoverageReport:summary", - "api/RouteCoverageReport:timestamp", "api/RouteDefinition:category", "api/RouteDefinition:description", "api/RouteDefinition:handler", diff --git a/packages/spec/declaration-map/api.json b/packages/spec/declaration-map/api.json index d8e1da6fd1..315a3bca85 100644 --- a/packages/spec/declaration-map/api.json +++ b/packages/spec/declaration-map/api.json @@ -380,8 +380,6 @@ "GetUiViewRequestSchema": "api/GetUiViewRequest", "GetUiViewResponse": "api/GetUiViewResponse", "GetUiViewResponseSchema": "api/GetUiViewResponse", - "HandlerStatus": "api/HandlerStatus", - "HandlerStatusSchema": "api/HandlerStatus", "HistoryMetaItemRequest": "api/HistoryMetaItemRequest", "HistoryMetaItemRequestSchema": "api/HistoryMetaItemRequest", "HistoryMetaItemResponse": "api/HistoryMetaItemResponse", @@ -656,10 +654,6 @@ "RollbackToPackageCommitResponse": "api/RollbackToPackageCommitResponse", "RollbackToPackageCommitResponseSchema": "api/RollbackToPackageCommitResponse", "RouteCategory": "api/RouteCategory", - "RouteCoverageEntry": "api/RouteCoverageEntry", - "RouteCoverageEntrySchema": "api/RouteCoverageEntry", - "RouteCoverageReport": "api/RouteCoverageReport", - "RouteCoverageReportSchema": "api/RouteCoverageReport", "RouteDefinition": "api/RouteDefinition", "RouteDefinitionSchema": "api/RouteDefinition", "RouteGenerationConfig": "api/RouteGenerationConfig", diff --git a/packages/spec/export-origins/api.json b/packages/spec/export-origins/api.json index 36e01f542d..2d4387df9a 100644 --- a/packages/spec/export-origins/api.json +++ b/packages/spec/export-origins/api.json @@ -495,8 +495,6 @@ "GetUiViewResponse": "src/api/protocol.zod.ts#GetUiViewResponse (type)", "GetUiViewResponseParsed": "src/api/protocol.zod.ts#GetUiViewResponseParsed (type)", "GetUiViewResponseSchema": "src/api/protocol.zod.ts#GetUiViewResponseSchema (const)", - "HandlerStatus": "src/api/plugin-rest-api.zod.ts#HandlerStatus (type)", - "HandlerStatusSchema": "src/api/plugin-rest-api.zod.ts#HandlerStatusSchema (const)", "HistoryMetaItemRequest": "src/api/protocol.zod.ts#HistoryMetaItemRequest (type)", "HistoryMetaItemRequestSchema": "src/api/protocol.zod.ts#HistoryMetaItemRequestSchema (const)", "HistoryMetaItemResponse": "src/api/protocol.zod.ts#HistoryMetaItemResponse (type)", @@ -878,10 +876,6 @@ "RollbackToPackageCommitResponseParsed": "src/api/package-lifecycle.zod.ts#RollbackToPackageCommitResponseParsed (type)", "RollbackToPackageCommitResponseSchema": "src/api/package-lifecycle.zod.ts#RollbackToPackageCommitResponseSchema (const)", "RouteCategory": "src/api/router.zod.ts#RouteCategory (type)", - "RouteCoverageEntry": "src/api/plugin-rest-api.zod.ts#RouteCoverageEntry (type)", - "RouteCoverageEntrySchema": "src/api/plugin-rest-api.zod.ts#RouteCoverageEntrySchema (const)", - "RouteCoverageReport": "src/api/plugin-rest-api.zod.ts#RouteCoverageReport (type)", - "RouteCoverageReportSchema": "src/api/plugin-rest-api.zod.ts#RouteCoverageReportSchema (const)", "RouteDefinition": "src/api/router.zod.ts#RouteDefinition (type)", "RouteDefinitionParsed": "src/api/router.zod.ts#RouteDefinitionParsed (type)", "RouteDefinitionSchema": "src/api/router.zod.ts#RouteDefinitionSchema (const)", diff --git a/packages/spec/json-schema.manifest/api.json b/packages/spec/json-schema.manifest/api.json index 2d56c5e21e..0b59138f15 100644 --- a/packages/spec/json-schema.manifest/api.json +++ b/packages/spec/json-schema.manifest/api.json @@ -207,7 +207,6 @@ "api/GetTranslationsResponse", "api/GetUiViewRequest", "api/GetUiViewResponse", - "api/HandlerStatus", "api/HistoryMetaItemRequest", "api/HistoryMetaItemResponse", "api/HttpFindQueryParams", @@ -362,8 +361,6 @@ "api/RollbackMetaItemResponse", "api/RollbackToPackageCommitResponse", "api/RouteCategory", - "api/RouteCoverageEntry", - "api/RouteCoverageReport", "api/RouteDefinition", "api/RouteGenerationConfig", "api/RouteHealthEntry", diff --git a/packages/spec/src/api/plugin-rest-api.handler-status-retirement.test.ts b/packages/spec/src/api/plugin-rest-api.handler-status-retirement.test.ts new file mode 100644 index 0000000000..bd3d30eccb --- /dev/null +++ b/packages/spec/src/api/plugin-rest-api.handler-status-retirement.test.ts @@ -0,0 +1,221 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import { describe, it, expect } from 'vitest'; + +import { + RestApiEndpointSchema, + RestApiRouteRegistrationSchema, + getDefaultRouteRegistrations, + type RestApiEndpoint, +} from './plugin-rest-api.zod'; +import { + MIGRATIONS_BY_MAJOR, + RETIRED_DEFS_BY_MAJOR, + RETIRED_KEYS_BY_MAJOR, +} from '../migrations/registry'; +import { + EXPORT_ENTRY_POINTS, + exportNamesOf, + holdersOf, +} from '../../scripts/lib/export-origins-testkit'; + +// ─── [#13823] `RestApiEndpoint.handlerStatus` and the Route Coverage Report +// are RETIRED ─────────────────────────────────────────────────────────── +// +// ADR-0049 enforce-or-remove; maintainer ruling 2026-09-01 (director decision +// batch #27, verbatim 「同意」: remove; enforce excluded). The key +// (`implemented` / `stub` / `planned`) was documented to make a `stub` +// handler "return 501 Not Implemented", and NOTHING read it: every +// `DispatcherErrorCode.enum.NOT_IMPLEMENTED` site is the declarative-endpoint +// executor refusing a target or mapping it cannot serve, none consulting the +// key. So `handlerStatus: 'stub'` got an ordinarily served route, and the +// `RouteCoverageReportSchema` that would have carried the status outward had +// zero constructors in objectstack, objectui (pinned sha) or cloud. +// +// Three bookkeeping shapes, pinned below: +// +// 1. `handlerStatus:` — `retiredKey()` tombstone on the non-strict +// `RestApiEndpointSchema` (a bare deletion would be a SILENT STRIP, +// #3733 / ADR-0104); `api/RestApiEndpoint:handlerStatus` in +// `RETIRED_KEYS_BY_MAJOR[18]`. +// 2. `RouteCoverageEntrySchema` / `RouteCoverageReportSchema` + their two +// types — whole-def removal (route 3: nobody ever parsed or constructed +// one); `api/RouteCoverageEntry` + `api/RouteCoverageReport` in +// `RETIRED_DEFS_BY_MAJOR[18]`. +// 3. `HandlerStatusSchema` / `HandlerStatus` — orphan value enum once both +// carriers are gone (#3950); `api/HandlerStatus` in +// `RETIRED_DEFS_BY_MAJOR[18]`. +// +// No D2 conversion, deliberately: nothing in the tree parses +// `RestApiEndpointSchema` outside its own unit tests — a REST API plugin route +// registration is not a stack collection member and never a `sys_metadata` +// row — so the conversion chain has no seam that would ever see one (the +// `kernel/Manifest:loading` precedent). The D3 semantic entry +// `rest-api-endpoint-handler-status-retired` carries the prescription. +// +// On the assertion set (the #8586 / #11846 precedent): a schema refusal +// raises a `ZodError` whose issues carry `code` and `path` but no ADR-0112 +// `status` — that envelope belongs to the API error surface. So these pins +// assert the strongest set this surface really has: refusal, the issue +// `code`, the `path` naming WHICH site refused, and the prescription text +// (#5240: where the wording is the contract, pin the wording). + +/** A well-formed endpoint — every required key, none of the retired one. */ +const WELL_FORMED = { + method: 'POST', + path: '/api/v1/cases/:id/close', + handler: 'closeCase', + category: 'data', +} as const; + +const PRESCRIPTION = /`RestApiEndpoint\.handlerStatus`.*was removed.*17/s; + +describe('[#13823] RestApiEndpoint.handlerStatus retirement', () => { + // All three former values, INCLUDING the documented default: the old + // docblock's `@default 'implemented'` was prose only — the key never carried + // a Zod `.default()`, so no built artifact materialised it and there is no + // residue window to tolerate (#12840 does not apply). Each is refused alike. + it.each(['implemented', 'stub', 'planned'] as const)( + "REJECTS handlerStatus: '%s' at path `handlerStatus`, carrying the prescription", + (value) => { + const result = RestApiEndpointSchema.safeParse({ ...WELL_FORMED, handlerStatus: value }); + expect(result.success).toBe(false); + if (result.success) return; // narrowing; the assertion above already failed + + const issue = result.error.issues.find((i) => i.path[0] === 'handlerStatus'); + expect(issue, 'the refusal must name `handlerStatus`').toBeDefined(); + // The machine-readable half of the envelope this surface actually has: + // a `retiredKey()` tombstone raises `invalid_type` from its `z.never()`. + expect(issue!.code).toBe('invalid_type'); + expect(issue!.path).toEqual(['handlerStatus']); + // The prescription IS the migration doc for whoever hits it — contract, + // not commentary: it names the key, says it was removed, explains why + // it was inert, and tells the author what to do. + expect(issue!.message).toMatch(PRESCRIPTION); + expect(issue!.message).toMatch(/nothing ever read it/s); + expect(issue!.message).toMatch(/Delete the key/s); + // The live mechanism must be named: the 501 comes from the executor. + expect(issue!.message).toMatch(/501 NOT_IMPLEMENTED.*declarative-endpoint executor/s); + // And the ruled-out alternative, so nobody re-declares it as a repair. + expect(issue!.message).toMatch(/not a platform capability/s); + // Customer-facing text carries the ADR, never an issue id — a `#NNNN` + // token resolves to nothing for the reader who meets this refusal + // (`check:doc-authoring`); the durable reference is ADR-0049. + expect(issue!.message).toMatch(/ADR-0049/); + expect(issue!.message).not.toMatch(/#\d{3,}/); + }, + ); + + it('REJECTS it through the route-registration embed too, at the nested path', () => { + const result = RestApiRouteRegistrationSchema.safeParse({ + prefix: '/api/v1/cases', + service: 'cases', + category: 'data', + endpoints: [{ ...WELL_FORMED, handlerStatus: 'stub' }], + }); + expect(result.success).toBe(false); + if (result.success) return; + + const issue = result.error.issues.find((i) => i.path.join('.') === 'endpoints.0.handlerStatus'); + expect(issue, 'the refusal must surface through `endpoints[]`').toBeDefined(); + expect(issue!.code).toBe('invalid_type'); + expect(issue!.path).toEqual(['endpoints', 0, 'handlerStatus']); + expect(issue!.message).toMatch(PRESCRIPTION); + }); + + it('parses a well-formed endpoint without the key and grows no `handlerStatus` property', () => { + const parsed = RestApiEndpointSchema.parse({ ...WELL_FORMED }); + expect(parsed.handler).toBe('closeCase'); + expect(parsed.public).toBe(false); // control: the live defaults still apply + expect(parsed.cacheable).toBe(false); + // The non-strict strip path: absence must stay absence. If the tombstone + // were ever replaced by a plain deletion, an authored `handlerStatus` + // would be stripped here in silence — this pin plus the rejections above + // are what make that regression loud. + expect(parsed).not.toHaveProperty('handlerStatus'); + }); + + it('the shipped default route registrations never carried the key and still parse', () => { + const groups = getDefaultRouteRegistrations(); + expect(groups.length).toBe(8); // anti-vacuity: the real shipped set + for (const group of groups) { + const result = RestApiRouteRegistrationSchema.safeParse(group); + expect(result.success, `${group.prefix} must still parse`).toBe(true); + } + }); + + it('fails tsc at the authoring site: the input type of the key is `never`', () => { + const endpoint: RestApiEndpoint = { + ...WELL_FORMED, + // @ts-expect-error — `handlerStatus` is a retiredKey() tombstone: its + // input type is `never`, so a typed literal cannot carry it (#13823). + handlerStatus: 'implemented', + }; + // The parse channel agrees with the type channel on the same literal. + expect(RestApiEndpointSchema.safeParse(endpoint).success).toBe(false); + }); +}); + +describe('[#13823] api/HandlerStatus + api/RouteCoverage{Entry,Report} def retirement', () => { + /** The 6 names the three retired defs exported (3 schema consts + 3 types). */ + const RETIRED_NAMES = [ + 'HandlerStatusSchema', + 'HandlerStatus', + 'RouteCoverageEntrySchema', + 'RouteCoverageEntry', + 'RouteCoverageReportSchema', + 'RouteCoverageReport', + ] as const; + + it('every retired name has ZERO holders on any public entry; the carriers survive', () => { + // Anti-vacuity: the baseline must cover the real surface. + for (const needed of ['.', './api']) { + expect(EXPORT_ENTRY_POINTS, `exports map must include ${needed}`).toContain(needed); + } + expect(exportNamesOf('./api').length, './api must export a non-trivial surface').toBeGreaterThan(50); + + // ── ABSENCE (every entry, not just ./api) ───────────────────────────── + for (const name of RETIRED_NAMES) { + expect(holdersOf(name), `${name} must have zero holders after #13823`).toEqual([]); + } + + // ── SURVIVAL ────────────────────────────────────────────────────────── + // The plugin module itself stays: the carrier def and its neighbours are + // untouched — this retirement is a narrowing, not a module sweep. + const apiNames = exportNamesOf('./api'); + for (const name of [ + 'RestApiEndpointSchema', + 'RestApiRouteRegistrationSchema', + 'RestApiPluginConfigSchema', + 'RestApiRouteCategory', + 'getDefaultRouteRegistrations', + ]) { + expect(apiNames, `${name} must SURVIVE this retirement`).toContain(name); + } + }); + + it('the api barrel resolves without the retired schemas and keeps the survivors', async () => { + const api = await import('./index'); + expect(api).not.toHaveProperty('HandlerStatusSchema'); + expect(api).not.toHaveProperty('RouteCoverageEntrySchema'); + expect(api).not.toHaveProperty('RouteCoverageReportSchema'); + // Anti-vacuity: the barrel really resolved and still exports the carrier. + expect(api).toHaveProperty('RestApiEndpointSchema'); + expect(api).toHaveProperty('RestApiRouteRegistrationSchema'); + }); +}); + +describe('[#13823] ADR-0087 registration', () => { + it('declares the tombstoned key and the three removed defs under major 18, with the D3 entry', () => { + expect(RETIRED_KEYS_BY_MAJOR[18]).toContain('api/RestApiEndpoint:handlerStatus'); + for (const def of ['api/HandlerStatus', 'api/RouteCoverageEntry', 'api/RouteCoverageReport']) { + expect(RETIRED_DEFS_BY_MAJOR[18], `${def} must be declared`).toContain(def); + } + const step = MIGRATIONS_BY_MAJOR[18]; + expect(step.semantic.map((m) => m.id)).toContain('rest-api-endpoint-handler-status-retired'); + // No D2 conversion by design (no seam ever parses the schema) — a + // conversion id appearing here would mean someone wired a transform that + // never runs; see the entry file for the reasoning. + expect(step.conversionIds).not.toContain('rest-api-endpoint-handler-status-removed'); + }); +}); diff --git a/packages/spec/src/api/plugin-rest-api.zod.ts b/packages/spec/src/api/plugin-rest-api.zod.ts index 2cebeab917..b7f5c5e018 100644 --- a/packages/spec/src/api/plugin-rest-api.zod.ts +++ b/packages/spec/src/api/plugin-rest-api.zod.ts @@ -3,6 +3,7 @@ import { z } from 'zod'; import { HttpMethod } from '../shared/http.zod'; import { MiddlewareConfigSchema } from '../system/http-server.zod'; +import { retiredKey } from '../shared/retired-key'; /** * REST API Plugin Protocol @@ -87,17 +88,55 @@ export type RestApiRouteCategory = z.input; // Route Registration Schema // ========================================== -/** - * Handler Implementation Status - * Shared enum for tracking whether an endpoint has a real handler. - * Used by both `RestApiEndpointSchema` and `RouteCoverageEntrySchema`. - * - * - `implemented` – A real handler is coded and registered. - * - `stub` – A placeholder handler exists that returns 501 Not Implemented. - * - `planned` – Declared in the protocol spec but not yet implemented. - */ -export const HandlerStatusSchema = lazySchema(() => z.enum(['implemented', 'stub', 'planned'])); -export type HandlerStatus = z.input; +// ─── [#13823] `handlerStatus` and the Route Coverage Report are RETIRED ────── +// +// ADR-0049 enforce-or-remove; maintainer ruling 2026-09-01 (director decision +// batch #27, verbatim 「同意」): remove. `handlerStatus` (`implemented` / `stub` +// / `planned`) was authorable on `RestApiEndpointSchema` and re-declared on +// `RouteCoverageEntrySchema`, and NOTHING read it: a repo-wide identifier +// search at this retirement's base (a9b2be0b0; `skills/**` and tests +// excluded) returned only the three sites that used to sit in this file. Its +// documented effect had a different cause — `DispatcherErrorCode.enum +// .NOT_IMPLEMENTED` is raised by the declarative-endpoint executor +// (`runtime/src/endpoint-executor.ts` ×3, `runtime/src/api-mapping.ts`, +// `runtime/src/api-endpoint-step.ts`) for a target or mapping it cannot +// serve, and none of those sites consults the key — so `handlerStatus: +// 'stub'` got an ordinarily served route, and `RouteCoverageReportSchema`, +// the only shape that would have carried the status outward, had zero +// constructors in objectstack, objectui (pinned sha) and cloud. +// +// Three bookkeeping shapes, one retirement: +// 1. `RestApiEndpoint.handlerStatus` — `retiredKey()` tombstone below (this +// shape is a non-strict `z.object`, so a bare deletion would be a silent +// strip, #3733 / ADR-0104); `api/RestApiEndpoint:handlerStatus` in +// `RETIRED_KEYS_BY_MAJOR[18]`. +// 2. `RouteCoverageEntrySchema` / `RouteCoverageReportSchema` — whole-def +// removal (route 3: nobody ever parsed or constructed one); +// `api/RouteCoverageEntry` + `api/RouteCoverageReport` in +// `RETIRED_DEFS_BY_MAJOR[18]`. The section that declared them is +// recorded at the end of this file. +// 3. `HandlerStatusSchema` / `HandlerStatus` (the enum this comment +// replaces) — orphan value schema once both carriers are gone (#3950: +// an exported value schema with no consumer reads as a capability); +// `api/HandlerStatus` in `RETIRED_DEFS_BY_MAJOR[18]`. +// +// No D2 conversion, deliberately: nothing in the tree parses +// `RestApiEndpointSchema` outside its own unit tests — it is not a stack +// collection member (`PLURAL_TO_SINGULAR` has no entry for it) and never a +// `sys_metadata` row — so a MetadataConversion would be a transform with no +// seam that ever runs (the `kernel/Manifest:loading` disposition). The D3 +// semantic entry `rest-api-endpoint-handler-status-retired` carries the +// prescription outward. ENFORCE — mounting a 501 stub for `stub` / `planned` +// — was ruled out as a zero-pull new capability, not a repair. +const HANDLER_STATUS_RETIRED = + '`RestApiEndpoint.handlerStatus` was removed in @objectstack/spec 17 (ADR-0049 ' + + 'enforce-or-remove) — nothing ever read it: no registrar, dispatcher or adapter consulted ' + + "the key, so an endpoint declared `stub` or `planned` was served exactly like an " + + '`implemented` one, and the `501 NOT_IMPLEMENTED` its docstring promised is raised by the ' + + 'declarative-endpoint executor for a target it cannot serve, never from this field. Delete ' + + 'the key. An endpoint that has no handler yet is simply not registered; a ' + + 'declared-but-unbuilt route answering 501 is not a platform capability (ruling record, ' + + '2026-09-01).'; /** * REST API Endpoint Schema @@ -166,16 +205,16 @@ export const RestApiEndpointSchema = lazySchema(() => z.object({ cacheTtl: z.number().int().optional().describe('Cache TTL in seconds'), /** - * Handler implementation status. - * Tracks whether this endpoint has a real handler or is only declared. - * - * - `implemented` – A real handler is coded and registered. - * - `stub` – A placeholder handler exists that returns 501 Not Implemented. - * - `planned` – Declared in the protocol spec but not yet implemented. - * @default 'implemented' + * RETIRED (#13823, ADR-0049): `handlerStatus` (`implemented` / `stub` / + * `planned`) had no reader — the retirement record sits above + * `HANDLER_STATUS_RETIRED`. Tombstoned rather than deleted: this shape is + * not `.strict()`, so authoring it must fail loudly (tsc `never` + the + * parse-time prescription), never strip in silence. The old docblock's + * `@default 'implemented'` was prose only — the key never carried a Zod + * `.default()`, so no built artifact materialised it and there is no + * residue window to tolerate (#12840 does not apply). */ - handlerStatus: HandlerStatusSchema.optional() - .describe('Handler implementation status: implemented (default if omitted), stub, or planned'), + handlerStatus: retiredKey(HANDLER_STATUS_RETIRED), })); export type RestApiEndpoint = z.input; @@ -1397,54 +1436,18 @@ export function getDefaultRouteRegistrations(): RestApiRouteRegistration[] { } // ========================================== -// Route Coverage Report +// Route Coverage Report — RETIRED (#13823) // ========================================== - -/** - * Route Coverage Entry Schema - * Reports the coverage status of a single declared endpoint. - */ -export const RouteCoverageEntrySchema = z.object({ - /** Full URL path of the endpoint */ - path: z.string().describe('Full URL path (e.g. /api/v1/analytics/query)'), - /** HTTP method */ - method: HttpMethod.describe('HTTP method (GET, POST, etc.)'), - /** Route category */ - category: RestApiRouteCategory.describe('Route category'), - /** Handler implementation status */ - handlerStatus: HandlerStatusSchema.describe('Handler status'), - /** Target service */ - service: z.string().describe('Target service name'), - /** Whether the handler was successfully called during health check */ - healthCheckPassed: z.boolean().optional().describe('Whether the health check probe succeeded'), -}); - -export type RouteCoverageEntry = z.input; - -/** - * Route Coverage Report Schema - * - * Aggregated report generated by the adapter/dispatcher at startup. - * Lists every declared endpoint and whether a handler is confirmed. - * - * Adapters SHOULD log a warning for every endpoint where - * `handlerStatus !== 'implemented'` and emit this report as part - * of the startup health diagnostics. - */ -export const RouteCoverageReportSchema = z.object({ - /** ISO 8601 timestamp of report generation */ - timestamp: z.string().describe('ISO 8601 timestamp'), - /** Adapter that generated the report */ - adapter: z.string().describe('Adapter name (e.g. "hono", "express", "nextjs")'), - /** Summary counters */ - summary: z.object({ - total: z.number().int().describe('Total declared endpoints'), - implemented: z.number().int().describe('Endpoints with real handlers'), - stub: z.number().int().describe('Endpoints with stub handlers (501)'), - planned: z.number().int().describe('Endpoints not yet implemented'), - }), - /** Per-endpoint entries */ - entries: z.array(RouteCoverageEntrySchema).describe('Per-endpoint coverage entries'), -}); - -export type RouteCoverageReport = z.input; +// +// `RouteCoverageEntrySchema` / `RouteCoverageReportSchema` (and their +// `RouteCoverageEntry` / `RouteCoverageReport` types) left the published set +// whole in this retirement — the record is above `HANDLER_STATUS_RETIRED`. +// The docblock that stood here said adapters SHOULD warn on every endpoint +// with `handlerStatus !== 'implemented'` and emit the report as startup +// health diagnostics; no adapter, dispatcher or registrar ever constructed +// one, so the report was a shape with no producer and the status it +// aggregated had no reader. Registered as `api/RouteCoverageEntry` and +// `api/RouteCoverageReport` in `RETIRED_DEFS_BY_MAJOR[18]`. Route readiness +// that IS measured is unchanged and lives elsewhere: the discovery payload's +// per-service `status` / `handlerReady` (`api/discovery.zod.ts`) and the +// CI-asserted route ledger (`packages/runtime/src/route-ledger.ts`). diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__HandlerStatus.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__HandlerStatus.ts new file mode 100644 index 0000000000..cabc257ee9 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__HandlerStatus.ts @@ -0,0 +1,12 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13823 — `api/HandlerStatus` (the `implemented` / `stub` / `planned` enum) +// left with its two carriers: `RestApiEndpoint.handlerStatus` is tombstoned +// in this same major (`RETIRED_KEYS_BY_MAJOR[18]`) and +// `RouteCoverageEntry.handlerStatus` left with that def (`api/RouteCoverageEntry` +// below), so the enum had no remaining consumer — and an exported value +// schema with no consumer reads as a capability (#3950, the `ui/ThemeMode` +// rule). Measured before removal: zero readers of the enum or the key in +// objectstack, objectui (pinned sha) or cloud. See +// `18.api__RestApiEndpoint__handlerStatus.ts` for the retirement record. +export const entry = 'api/HandlerStatus'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__RouteCoverageEntry.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__RouteCoverageEntry.ts new file mode 100644 index 0000000000..703c7bc844 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__RouteCoverageEntry.ts @@ -0,0 +1,11 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13823 — `api/RouteCoverageEntry` (one declared endpoint's coverage row: +// `path` / `method` / `category` / `handlerStatus` / `service` / +// `healthCheckPassed`) left whole with `api/RouteCoverageReport`, the only +// shape that embedded it. Nothing ever constructed or parsed one — zero +// constructors in objectstack, objectui (pinned sha) or cloud — so the +// `handlerStatus` it re-declared was carried outward by nobody. Route 3 +// (whole-def removal, no carrier key, no D2 conversion); see +// `18.api__RestApiEndpoint__handlerStatus.ts` for the retirement record. +export const entry = 'api/RouteCoverageEntry'; diff --git a/packages/spec/src/migrations/entries/retired-defs/18.api__RouteCoverageReport.ts b/packages/spec/src/migrations/entries/retired-defs/18.api__RouteCoverageReport.ts new file mode 100644 index 0000000000..cc06bf00fd --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-defs/18.api__RouteCoverageReport.ts @@ -0,0 +1,15 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13823 — `api/RouteCoverageReport` (the aggregated startup report — `timestamp` +// / `adapter` / `summary { total, implemented, stub, planned }` / `entries[]`) +// whose docblock said adapters SHOULD emit it as startup health diagnostics and +// warn on every endpoint with `handlerStatus !== 'implemented'`. No adapter, +// dispatcher or registrar ever constructed one — zero constructors in +// objectstack, objectui (pinned sha) or cloud — so it was a shape with no +// producer, and the status it aggregated had no reader. Route 3 (whole-def +// removal, no carrier key, no D2 conversion). Route readiness that IS measured +// is unchanged: the discovery payload's per-service `status` / `handlerReady` +// (`api/discovery.zod.ts`) and the CI-asserted route ledger +// (`packages/runtime/src/route-ledger.ts`). See +// `18.api__RestApiEndpoint__handlerStatus.ts` for the retirement record. +export const entry = 'api/RouteCoverageReport'; diff --git a/packages/spec/src/migrations/entries/retired-keys/18.api__RestApiEndpoint__handlerStatus.ts b/packages/spec/src/migrations/entries/retired-keys/18.api__RestApiEndpoint__handlerStatus.ts new file mode 100644 index 0000000000..750980c627 --- /dev/null +++ b/packages/spec/src/migrations/entries/retired-keys/18.api__RestApiEndpoint__handlerStatus.ts @@ -0,0 +1,38 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +// #13823 — ADR-0049 enforce-or-remove on `RestApiEndpointSchema.handlerStatus` +// (maintainer ruling 2026-09-01, director decision batch #27, verbatim +// 「同意」: remove). The key (`implemented` / `stub` / `planned`) was declared +// as a handler-readiness marker whose docstring promised that a `stub` handler +// "returns 501 Not Implemented", and NOTHING read it: the only identifier hits +// outside `skills/**` and tests were the declaration, its re-declaration on +// `RouteCoverageEntrySchema` and a docblock saying adapters SHOULD warn on it. +// The 501 it described has a different cause — every +// `DispatcherErrorCode.enum.NOT_IMPLEMENTED` site (`runtime/src/ +// endpoint-executor.ts` ×3, `runtime/src/api-mapping.ts`, +// `runtime/src/api-endpoint-step.ts`) is the declarative-endpoint executor +// refusing a target or mapping it cannot serve, none consulting the key — so +// `handlerStatus: 'stub'` got an ordinarily served route and reported its +// progress to nobody. Tombstoned with `retiredKey()`: `RestApiEndpointSchema` +// is a non-strict `z.object`, so a bare deletion would be a silent strip +// (#3733, ADR-0104). The value enum it was typed with and the two report +// shapes that re-declared it leave whole — `api/HandlerStatus`, +// `api/RouteCoverageEntry`, `api/RouteCoverageReport` in +// `RETIRED_DEFS_BY_MAJOR[18]`. +// +// Registered here but NOT in `src/conversions/registry.ts`, the +// `kernel/Manifest:loading` reasoning: nothing in the tree parses +// `RestApiEndpointSchema` outside its own unit tests — a REST API plugin's +// route registration is not a stack collection member (`PLURAL_TO_SINGULAR` +// has no entry for it) and nothing stores one as a `sys_metadata` row — so a +// MetadataConversion would be a transform with no seam that ever runs. The +// prescription reaches authors through the tombstone plus the D3 semantic +// entry `rest-api-endpoint-handler-status-retired`. ENFORCE (mounting a 501 +// stub for `stub` / `planned`) was excluded by the same ruling as a zero-pull +// new capability, not a repair. +// +// Registered under 18, not 17: v17.0.0 was cut before this landed, so the +// removal ships on the 17.x line (launch-window convention: accept-set +// narrowings ride minor releases) and the prescription lives at the major +// boundary where `migrate meta` users look (the #11846 / #12428 grading). +export const entry = 'api/RestApiEndpoint:handlerStatus'; diff --git a/packages/spec/src/migrations/entries/semantic/18.rest-api-endpoint-handler-status-retired.ts b/packages/spec/src/migrations/entries/semantic/18.rest-api-endpoint-handler-status-retired.ts new file mode 100644 index 0000000000..d18d3f7169 --- /dev/null +++ b/packages/spec/src/migrations/entries/semantic/18.rest-api-endpoint-handler-status-retired.ts @@ -0,0 +1,85 @@ +// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license. + +import type { SemanticMigration } from '../../types.js'; + +export const entry: SemanticMigration = { + id: 'rest-api-endpoint-handler-status-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'RestApiEndpoint.handlerStatus (the implemented / stub / planned marker an ' + + 'endpoint in a REST API plugin route registration could carry), the ' + + 'HandlerStatusSchema / HandlerStatus value def it was typed with, and the ' + + 'RouteCoverageEntrySchema / RouteCoverageReportSchema report shapes (with ' + + 'their RouteCoverageEntry / RouteCoverageReport types) that re-declared it', + replacement: + 'nothing declarative — the key never changed what the platform served, so ' + + 'there is no working configuration to migrate to. Delete the key; an ' + + 'endpoint that has no handler yet is simply not registered. Route ' + + 'readiness that IS measured is unchanged and lives elsewhere: the ' + + "discovery payload reports each service's status and handlerReady " + + '(api/discovery.zod.ts), and packages/runtime/src/route-ledger.ts asserts ' + + 'per-route coverage in CI. A declared-but-unbuilt route answering 501 ' + + 'instead of 404 is a new capability the ruling explicitly excluded (zero ' + + 'pull); if it is ever wanted it re-declares fresh under its own ruling, ' + + 'executor first', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-01 on #13823 ' + + '(director decision batch #27, verbatim 「同意」: remove; enforce ' + + 'excluded). The key was DOCUMENTED to cause a specific runtime behaviour ' + + '— its docstring said a stub handler "returns 501 Not Implemented" — and ' + + 'that behaviour has a different cause: every ' + + 'DispatcherErrorCode.enum.NOT_IMPLEMENTED site (runtime/src/' + + 'endpoint-executor.ts ×3, runtime/src/api-mapping.ts, ' + + 'runtime/src/api-endpoint-step.ts) is the declarative-endpoint executor ' + + 'refusing a target or mapping it cannot serve, and none of them consults ' + + 'handlerStatus. Measured at the retirement base (origin/main a9b2be0b0, ' + + '2026-09-02, skills/** and tests excluded): the only identifier hits were ' + + 'the declaration on RestApiEndpointSchema, the re-declaration on ' + + 'RouteCoverageEntrySchema and a docblock saying adapters SHOULD warn on ' + + 'it; RouteCoverageReportSchema — the one shape that would have carried ' + + 'the status outward — had zero constructors in objectstack, objectui ' + + '(pinned sha) and cloud. So an author who wrote handlerStatus: \'stub\' ' + + 'expecting the dispatcher to answer 501 got an ordinarily served route, ' + + 'and the declaration reported progress to nobody — a declared ≠ enforced ' + + 'gap on the same endpoint vocabulary ApiEndpointSchema closed strictly in ' + + '#5384, and the surface a published skill had been teaching as working ' + + 'machinery (the sentence corrected in #13808 is where this card came ' + + 'from). Bookkeeping: the KEY is tombstoned with retiredKey() on the ' + + 'non-strict RestApiEndpointSchema (api/RestApiEndpoint:handlerStatus in ' + + 'RETIRED_KEYS_BY_MAJOR[18]); the DEFS leave whole — api/HandlerStatus ' + + '(orphan value enum once both carriers are gone, the #3950 rule), ' + + 'api/RouteCoverageEntry and api/RouteCoverageReport (route 3: nobody ' + + 'ever parsed or constructed one) — all three in RETIRED_DEFS_BY_MAJOR[18]. ' + + 'It is a SEMANTIC entry rather than a D2 conversion because there is no ' + + 'source to rewrite: nothing in the tree parses RestApiEndpointSchema ' + + 'outside its own unit tests — a REST API plugin route registration is not ' + + 'a stack collection member and never a sys_metadata row — so the ' + + 'conversion chain has no seam that would ever see one (the ' + + 'kernel/Manifest:loading disposition). ENFORCE was excluded by the ruling: ' + + 'mounting a 501 stub for stub / planned endpoints is a zero-pull new ' + + 'capability, not a repair. The same ruling records the class direction ' + + 'for the two sibling ADR-0049 cards (#13612 / #13613, not ruled by it): ' + + 'a declared-but-unenforced key with no pull retires; enforce/bind only on ' + + 'a named consumer or measured pull. ADR-0049 / ADR-0087, #13823.', + acceptanceCriteria: + 'No source writes handlerStatus on a RestApiEndpoint: authoring it is now a ' + + 'tsc error at the site (the tombstone types the key never) and a parse ' + + 'error carrying the prescription at path handlerStatus, for every former ' + + "value including the documented default 'implemented' (which was prose " + + 'only — the key never carried a Zod .default(), so no built artifact ' + + 'materialised it and there is no residue window). Pinned in ' + + 'api/plugin-rest-api.handler-status-retirement.test.ts. Concretely, check ' + + 'two places. (1) Every RestApiEndpoint literal — in a route registration ' + + 'passed to the REST API plugin, or standalone: delete the handlerStatus ' + + 'line; nothing served changes, because nothing ever read it. (2) Code ' + + 'importing HandlerStatusSchema, HandlerStatus, RouteCoverageEntrySchema, ' + + 'RouteCoverageEntry, RouteCoverageReportSchema or RouteCoverageReport from ' + + '@objectstack/spec or @objectstack/spec/api: every one is TS2305 after ' + + 'upgrade; no replacement exists to point at, because no producer ever ' + + 'emitted the report. Everything else on RestApiEndpointSchema — method, ' + + 'path, handler, category, public, permissions, the OpenAPI and ' + + 'performance keys — parses exactly as before, and the shipped default ' + + 'route registrations (getDefaultRouteRegistrations) never carried the ' + + 'key and still parse.', +}; diff --git a/packages/spec/src/migrations/registry.ts b/packages/spec/src/migrations/registry.ts index ec50867f19..cdbfd50a74 100644 --- a/packages/spec/src/migrations/registry.ts +++ b/packages/spec/src/migrations/registry.ts @@ -7441,6 +7441,87 @@ const step18: MigrationStep = { + '`collapsible: true` explicitly — an unset key now defers to the renderer, which does ' + 'not collapse.', }, + { + id: 'rest-api-endpoint-handler-status-retired', + // No backticks in `surface` — build-upgrade-guide.ts renders it inside a + // code span AND a table cell. + surface: + 'RestApiEndpoint.handlerStatus (the implemented / stub / planned marker an ' + + 'endpoint in a REST API plugin route registration could carry), the ' + + 'HandlerStatusSchema / HandlerStatus value def it was typed with, and the ' + + 'RouteCoverageEntrySchema / RouteCoverageReportSchema report shapes (with ' + + 'their RouteCoverageEntry / RouteCoverageReport types) that re-declared it', + replacement: + 'nothing declarative — the key never changed what the platform served, so ' + + 'there is no working configuration to migrate to. Delete the key; an ' + + 'endpoint that has no handler yet is simply not registered. Route ' + + 'readiness that IS measured is unchanged and lives elsewhere: the ' + + "discovery payload reports each service's status and handlerReady " + + '(api/discovery.zod.ts), and packages/runtime/src/route-ledger.ts asserts ' + + 'per-route coverage in CI. A declared-but-unbuilt route answering 501 ' + + 'instead of 404 is a new capability the ruling explicitly excluded (zero ' + + 'pull); if it is ever wanted it re-declares fresh under its own ruling, ' + + 'executor first', + reason: + 'ADR-0049 enforce-or-remove; maintainer ruling 2026-09-01 on #13823 ' + + '(director decision batch #27, verbatim 「同意」: remove; enforce ' + + 'excluded). The key was DOCUMENTED to cause a specific runtime behaviour ' + + '— its docstring said a stub handler "returns 501 Not Implemented" — and ' + + 'that behaviour has a different cause: every ' + + 'DispatcherErrorCode.enum.NOT_IMPLEMENTED site (runtime/src/' + + 'endpoint-executor.ts ×3, runtime/src/api-mapping.ts, ' + + 'runtime/src/api-endpoint-step.ts) is the declarative-endpoint executor ' + + 'refusing a target or mapping it cannot serve, and none of them consults ' + + 'handlerStatus. Measured at the retirement base (origin/main a9b2be0b0, ' + + '2026-09-02, skills/** and tests excluded): the only identifier hits were ' + + 'the declaration on RestApiEndpointSchema, the re-declaration on ' + + 'RouteCoverageEntrySchema and a docblock saying adapters SHOULD warn on ' + + 'it; RouteCoverageReportSchema — the one shape that would have carried ' + + 'the status outward — had zero constructors in objectstack, objectui ' + + '(pinned sha) and cloud. So an author who wrote handlerStatus: \'stub\' ' + + 'expecting the dispatcher to answer 501 got an ordinarily served route, ' + + 'and the declaration reported progress to nobody — a declared ≠ enforced ' + + 'gap on the same endpoint vocabulary ApiEndpointSchema closed strictly in ' + + '#5384, and the surface a published skill had been teaching as working ' + + 'machinery (the sentence corrected in #13808 is where this card came ' + + 'from). Bookkeeping: the KEY is tombstoned with retiredKey() on the ' + + 'non-strict RestApiEndpointSchema (api/RestApiEndpoint:handlerStatus in ' + + 'RETIRED_KEYS_BY_MAJOR[18]); the DEFS leave whole — api/HandlerStatus ' + + '(orphan value enum once both carriers are gone, the #3950 rule), ' + + 'api/RouteCoverageEntry and api/RouteCoverageReport (route 3: nobody ' + + 'ever parsed or constructed one) — all three in RETIRED_DEFS_BY_MAJOR[18]. ' + + 'It is a SEMANTIC entry rather than a D2 conversion because there is no ' + + 'source to rewrite: nothing in the tree parses RestApiEndpointSchema ' + + 'outside its own unit tests — a REST API plugin route registration is not ' + + 'a stack collection member and never a sys_metadata row — so the ' + + 'conversion chain has no seam that would ever see one (the ' + + 'kernel/Manifest:loading disposition). ENFORCE was excluded by the ruling: ' + + 'mounting a 501 stub for stub / planned endpoints is a zero-pull new ' + + 'capability, not a repair. The same ruling records the class direction ' + + 'for the two sibling ADR-0049 cards (#13612 / #13613, not ruled by it): ' + + 'a declared-but-unenforced key with no pull retires; enforce/bind only on ' + + 'a named consumer or measured pull. ADR-0049 / ADR-0087, #13823.', + acceptanceCriteria: + 'No source writes handlerStatus on a RestApiEndpoint: authoring it is now a ' + + 'tsc error at the site (the tombstone types the key never) and a parse ' + + 'error carrying the prescription at path handlerStatus, for every former ' + + "value including the documented default 'implemented' (which was prose " + + 'only — the key never carried a Zod .default(), so no built artifact ' + + 'materialised it and there is no residue window). Pinned in ' + + 'api/plugin-rest-api.handler-status-retirement.test.ts. Concretely, check ' + + 'two places. (1) Every RestApiEndpoint literal — in a route registration ' + + 'passed to the REST API plugin, or standalone: delete the handlerStatus ' + + 'line; nothing served changes, because nothing ever read it. (2) Code ' + + 'importing HandlerStatusSchema, HandlerStatus, RouteCoverageEntrySchema, ' + + 'RouteCoverageEntry, RouteCoverageReportSchema or RouteCoverageReport from ' + + '@objectstack/spec or @objectstack/spec/api: every one is TS2305 after ' + + 'upgrade; no replacement exists to point at, because no producer ever ' + + 'emitted the report. Everything else on RestApiEndpointSchema — method, ' + + 'path, handler, category, public, permissions, the OpenAPI and ' + + 'performance keys — parses exactly as before, and the shipped default ' + + 'route registrations (getDefaultRouteRegistrations) never carried the ' + + 'key and still parse.', + }, { id: 'scim-provider-object-retired', surface: @@ -8276,6 +8357,42 @@ export const RETIRED_KEYS_BY_MAJOR: Readonly> // entry id by `gen:migration-registry` (#7297). Add an entry by adding a // FILE — never by editing between the markers, which is generated. // + // #13823 — ADR-0049 enforce-or-remove on `RestApiEndpointSchema.handlerStatus` + // (maintainer ruling 2026-09-01, director decision batch #27, verbatim + // 「同意」: remove). The key (`implemented` / `stub` / `planned`) was declared + // as a handler-readiness marker whose docstring promised that a `stub` handler + // "returns 501 Not Implemented", and NOTHING read it: the only identifier hits + // outside `skills/**` and tests were the declaration, its re-declaration on + // `RouteCoverageEntrySchema` and a docblock saying adapters SHOULD warn on it. + // The 501 it described has a different cause — every + // `DispatcherErrorCode.enum.NOT_IMPLEMENTED` site (`runtime/src/ + // endpoint-executor.ts` ×3, `runtime/src/api-mapping.ts`, + // `runtime/src/api-endpoint-step.ts`) is the declarative-endpoint executor + // refusing a target or mapping it cannot serve, none consulting the key — so + // `handlerStatus: 'stub'` got an ordinarily served route and reported its + // progress to nobody. Tombstoned with `retiredKey()`: `RestApiEndpointSchema` + // is a non-strict `z.object`, so a bare deletion would be a silent strip + // (#3733, ADR-0104). The value enum it was typed with and the two report + // shapes that re-declared it leave whole — `api/HandlerStatus`, + // `api/RouteCoverageEntry`, `api/RouteCoverageReport` in + // `RETIRED_DEFS_BY_MAJOR[18]`. + // + // Registered here but NOT in `src/conversions/registry.ts`, the + // `kernel/Manifest:loading` reasoning: nothing in the tree parses + // `RestApiEndpointSchema` outside its own unit tests — a REST API plugin's + // route registration is not a stack collection member (`PLURAL_TO_SINGULAR` + // has no entry for it) and nothing stores one as a `sys_metadata` row — so a + // MetadataConversion would be a transform with no seam that ever runs. The + // prescription reaches authors through the tombstone plus the D3 semantic + // entry `rest-api-endpoint-handler-status-retired`. ENFORCE (mounting a 501 + // stub for `stub` / `planned`) was excluded by the same ruling as a zero-pull + // new capability, not a repair. + // + // Registered under 18, not 17: v17.0.0 was cut before this landed, so the + // removal ships on the 17.x line (launch-window convention: accept-set + // narrowings ride minor releases) and the prescription lives at the major + // boundary where `migrate meta` users look (the #11846 / #12428 grading). + 'api/RestApiEndpoint:handlerStatus', // #10414 — ADR-0049 enforce-or-remove (triage routed REMOVE; the #10298 shape // one level up). `filters` was a declared, authorable per-metric raw-SQL // filter (`filters: [{ sql: string }]`) with ZERO consumers, measured with a @@ -9591,6 +9708,16 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // entry id by `gen:migration-registry` (#7297). Add an entry by adding a // FILE — never by editing between the markers, which is generated. // + // #13823 — `api/HandlerStatus` (the `implemented` / `stub` / `planned` enum) + // left with its two carriers: `RestApiEndpoint.handlerStatus` is tombstoned + // in this same major (`RETIRED_KEYS_BY_MAJOR[18]`) and + // `RouteCoverageEntry.handlerStatus` left with that def (`api/RouteCoverageEntry` + // below), so the enum had no remaining consumer — and an exported value + // schema with no consumer reads as a capability (#3950, the `ui/ThemeMode` + // rule). Measured before removal: zero readers of the enum or the key in + // objectstack, objectui (pinned sha) or cloud. See + // `18.api__RestApiEndpoint__handlerStatus.ts` for the retirement record. + 'api/HandlerStatus', // #13135 — ADR-0049 enforce-or-remove (maintainer ruling 2026-08-29 on // #12057: retirement adopted, re-scope rejected; re-charter #13135 executes // the widened surface). Part of the whole-module removal of @@ -9682,6 +9809,28 @@ export const RETIRED_DEFS_BY_MAJOR: Readonly> // narrowings ride minor releases) and the prescription lives at the major // boundary where `migrate meta` users look (the #8586 / #8715 precedent). 'api/PackageRollbackResponse', + // #13823 — `api/RouteCoverageEntry` (one declared endpoint's coverage row: + // `path` / `method` / `category` / `handlerStatus` / `service` / + // `healthCheckPassed`) left whole with `api/RouteCoverageReport`, the only + // shape that embedded it. Nothing ever constructed or parsed one — zero + // constructors in objectstack, objectui (pinned sha) or cloud — so the + // `handlerStatus` it re-declared was carried outward by nobody. Route 3 + // (whole-def removal, no carrier key, no D2 conversion); see + // `18.api__RestApiEndpoint__handlerStatus.ts` for the retirement record. + 'api/RouteCoverageEntry', + // #13823 — `api/RouteCoverageReport` (the aggregated startup report — `timestamp` + // / `adapter` / `summary { total, implemented, stub, planned }` / `entries[]`) + // whose docblock said adapters SHOULD emit it as startup health diagnostics and + // warn on every endpoint with `handlerStatus !== 'implemented'`. No adapter, + // dispatcher or registrar ever constructed one — zero constructors in + // objectstack, objectui (pinned sha) or cloud — so it was a shape with no + // producer, and the status it aggregated had no reader. Route 3 (whole-def + // removal, no carrier key, no D2 conversion). Route readiness that IS measured + // is unchanged: the discovery payload's per-service `status` / `handlerReady` + // (`api/discovery.zod.ts`) and the CI-asserted route ledger + // (`packages/runtime/src/route-ledger.ts`). See + // `18.api__RestApiEndpoint__handlerStatus.ts` for the retirement record. + 'api/RouteCoverageReport', // #8715 — identity/identity.zod.ts `ApiKeySchema`, retired whole (ADR-0049 // enforce-or-remove; maintainer ruling 2026-08-15, disposition B: delete). // The schema documented better-auth's `apiKey` PLUGIN shape — a plugin this diff --git a/packages/spec/src/type-alias-convention.pin.test.ts b/packages/spec/src/type-alias-convention.pin.test.ts index 76498f61d1..8ec67c341b 100644 --- a/packages/spec/src/type-alias-convention.pin.test.ts +++ b/packages/spec/src/type-alias-convention.pin.test.ts @@ -269,7 +269,7 @@ import type * as M170 from './ui/component.zod.js'; import type * as M183 from './api/sortability.zod.js'; // --------------------------------------------------------------------------- -// 835 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. +// 832 isomorphic aliases: `z.input` === `z.infer`, so no `XParsed` is declared. // // That number is machine-checked, not hand-kept. The runtime companion at the // bottom of this file recomputes the pin count from the source and asserts that @@ -461,10 +461,7 @@ export type Iso121 = Assert, z.inf // api/plugin-rest-api.zod.ts export type Iso122 = Assert, z.infer< typeof M27.RestApiRouteCategory > >>; -export type Iso123 = Assert, z.infer< typeof M27.HandlerStatusSchema > >>; export type Iso124 = Assert, z.infer< typeof M27.ValidationMode > >>; -export type Iso125 = Assert, z.infer< typeof M27.RouteCoverageEntrySchema > >>; -export type Iso126 = Assert, z.infer< typeof M27.RouteCoverageReportSchema > >>; // api/protocol.zod.ts export type Iso127 = Assert, z.infer< typeof M28.GetDiscoveryRequestSchema > >>; @@ -1686,7 +1683,7 @@ describe('ADR-0122 type-alias convention', () => { // this title and the section header above the pin list — are now asserted // against the recomputed count below, so neither can go stale without a red // test naming it. - it('still declares all 835 isomorphic pins', () => { + it('still declares all 832 isomorphic pins', () => { // The truth of each pin is proved by tsc, not here — an `Assert>` // that stops holding is a compile error with the alias named. What tsc // cannot notice is a pin that was DELETED: removing the assertion removes @@ -2080,9 +2077,20 @@ describe('ADR-0122 type-alias convention', () => { // `element:number` left the isomorphic family the way ADR-0122 prescribes: // `ElementNumberPropsParsed` declared, the Iso818 pin deleted. -1 converted // to an `XParsed` pair; the Iso number stays vacant. + // + // 835 -> 832 is #13823's ADR-0049 retirement of `RestApiEndpoint.handlerStatus` + // and the Route Coverage Report (api/plugin-rest-api.zod.ts): the + // `HandlerStatusSchema` enum and the `RouteCoverageEntrySchema` / + // `RouteCoverageReportSchema` defs left the module whole, so their pins + // `Iso123` / `Iso125` / `Iso126` left with them — the aliases no longer + // exist, so there is nothing to be isomorphic. The carrier + // `RestApiEndpointSchema` keeps its `XParsed` pair (it has defaults), and + // `api/plugin-rest-api.handler-status-retirement.test.ts` asserts the + // absence of all six retired names on every public entry. -3 removed; the + // Iso numbers stay vacant (ids are claims about pins, not positions). const self = readFileSync(fileURLToPath(import.meta.url), 'utf8'); const pins = self.match(/^export type Iso\d+ = Assert