Skip to content

Commit 4eaea49

Browse files
committed
Merge remote-tracking branch 'origin/main' into claude/issue-14268-lint-suggest-consolidation
2 parents b86a754 + 53d3689 commit 4eaea49

19 files changed

Lines changed: 731 additions & 187 deletions

File tree

Lines changed: 91 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
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)
6+
7+
<!-- adr-0087: registered rest-api-endpoint-handler-status-retired -->
8+
9+
**BREAKING** accept-set narrowing and export removal, landing after the
10+
v17.0.0 cut (the lockstep launch-window convention ships it as `minor`; the
11+
prescription is registered under protocol major 18 —
12+
`RETIRED_KEYS_BY_MAJOR[18]` for the key, `RETIRED_DEFS_BY_MAJOR[18]` for the
13+
three defs, plus the D3 semantic entry
14+
`rest-api-endpoint-handler-status-retired` — where `os migrate meta` users
15+
will look). Maintainer ruling 2026-09-01 on #13823 (director decision batch
16+
#27, verbatim 「同意」): remove; enforce excluded.
17+
18+
`handlerStatus` (`implemented` / `stub` / `planned`) was an authorable key on
19+
`RestApiEndpointSchema` whose docstring promised that a `stub` handler
20+
"returns 501 Not Implemented", and **nothing read it**. Measured at the
21+
retirement base (`origin/main` a9b2be0b0, 2026-09-02, `skills/**` and tests
22+
excluded): the only identifier hits were the declaration, its re-declaration
23+
on `RouteCoverageEntrySchema` and a docblock saying adapters SHOULD warn on
24+
it. The 501 it described has a different cause — every
25+
`DispatcherErrorCode.enum.NOT_IMPLEMENTED` site (`runtime/src/endpoint-executor.ts`
26+
×3, `runtime/src/api-mapping.ts`, `runtime/src/api-endpoint-step.ts`) is the
27+
declarative-endpoint executor refusing a target or mapping it cannot serve,
28+
and none consults the key. So an author who wrote `handlerStatus: 'stub'`
29+
expecting a 501 got an ordinarily served route, and the declaration reported
30+
progress to nobody: `RouteCoverageReportSchema`, the only shape that would
31+
have carried it outward, had zero constructors in objectstack, objectui
32+
(pinned sha) and cloud.
33+
34+
FROM → TO:
35+
36+
- `handlerStatus: 'implemented' | 'stub' | 'planned'` on a `RestApiEndpoint`
37+
→ *(removed key)* — tombstoned with `retiredKey()` (the schema is not
38+
`.strict()`, so a bare deletion would be a silent strip): authoring it is
39+
now a `tsc` error and a parse error carrying the prescription at path
40+
`handlerStatus`, for every former value including the documented default
41+
`'implemented'` (prose only — the key never carried a Zod `.default()`, so
42+
no built artifact materialised it and there is no residue window).
43+
- `HandlerStatusSchema` / `HandlerStatus` → *(removed — no replacement)*. The
44+
enum's only two carriers leave in this same change; an exported value
45+
schema with no consumer reads as a capability (#3950).
46+
- `RouteCoverageEntrySchema` / `RouteCoverageEntry` and
47+
`RouteCoverageReportSchema` / `RouteCoverageReport` → *(removed — no
48+
replacement)*. No adapter, dispatcher or registrar ever constructed the
49+
report; it was a shape with no producer.
50+
51+
One-line fix: delete the key — nothing served changes, because nothing ever
52+
read it. An endpoint that has no handler yet is simply not registered. Route
53+
readiness that IS measured is untouched: the discovery payload's per-service
54+
`status` / `handlerReady` (`api/discovery.zod.ts`) and the CI-asserted route
55+
ledger (`packages/runtime/src/route-ledger.ts`). A declared-but-unbuilt route
56+
answering 501 instead of 404 is a new capability the ruling explicitly
57+
excluded (zero pull); if it is ever wanted it re-declares fresh under its own
58+
ruling, executor first.
59+
60+
The retirement kit:
61+
62+
- key tombstone at the declaration (`api/RestApiEndpoint:handlerStatus` in
63+
`RETIRED_KEYS_BY_MAJOR[18]`; the surface baseline line carries `[RETIRED]`)
64+
- whole-def deletions `api/HandlerStatus`, `api/RouteCoverageEntry`,
65+
`api/RouteCoverageReport` in `RETIRED_DEFS_BY_MAJOR[18]` (manifest keys
66+
deliberately removed; the #4725 gate adjudicated them)
67+
- deliberately NO D2 conversion: nothing in the tree parses
68+
`RestApiEndpointSchema` outside its own unit tests — a REST API plugin route
69+
registration is not a stack collection member and never a `sys_metadata`
70+
row — so the conversion chain has no seam that would ever see one (the
71+
`kernel/Manifest:loading` disposition); the D3 semantic entry carries the
72+
prescription, and for the same reason the tombstone carries no
73+
`os migrate meta` sentence
74+
- pin tests (`api/plugin-rest-api.handler-status-retirement.test.ts`): all
75+
three former values refused at path `handlerStatus` with the prescription,
76+
through the route-registration embed too; a well-formed endpoint without the
77+
key still parses and grows no `handlerStatus` property; the shipped default
78+
route registrations still parse; zero holders for all 6 retired export names
79+
on every public entry; the carrier schemas survive; the registrations under
80+
major 18 are present
81+
- no liveness-ledger row moves: `RestApiEndpointSchema` is outside the walked
82+
population (not a registered metadata type and not in `SPEC_ONLY_SCHEMAS`),
83+
so the verdict is recorded here, in the schema and in the D3 entry
84+
- teaching sweep: `content/docs/**`, `examples/**`, `packages/*/README.md` and
85+
`packages/create-objectstack/**` carry no hand-written mention; the generated
86+
reference page regenerates. The published skill `skills/objectstack-api`
87+
taught the key at this PR's base (a9b2be0b0); `main` already dropped that
88+
teaching in the #14448 optimization flight (bae4088f3 — zero `handlerStatus`
89+
/ Handler Status / RouteCoverage hits under `skills/` at 99b8faf8f, with
90+
`RestApiEndpoint` still hitting the skill as the live control), so no
91+
skills-lane follow-up is needed

‎apps/docs/app/[lang]/docs/[[...slug]]/page.tsx‎

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -55,14 +55,17 @@ type DocPage = NonNullable<ReturnType<typeof source.getPage>>;
5555
* `index` node. ⛔ Nothing here splits `page.url` on `/`, and nothing constructs a
5656
* URL the loader has not already produced.
5757
*
58-
* ⚠️ **An ancestor can arrive without a URL, and this drops it.** Fumadocs
59-
* attaches a folder's `index.mdx` as that folder's `index` node only when the
60-
* folder's `meta.json` does **not** list `"index"` in `pages`; a folder that
61-
* lists it reaches this walk with a `name` and no `url`. That was once 17 of the
62-
* 35 `meta.json` files under `content/docs`, shortening 172 of 404 trails. 16 of
63-
* the 17 were fixed producer-side (#12352) and 8 short trails remain, all under
64-
* `content/docs/releases/` — a directory AGENTS.md fences off, so its `meta.json`
65-
* still lists `"index"`. The condition is therefore live, just rare.
58+
* ⚠️ **An ancestor can arrive without a URL, and the loop below drops it.**
59+
* `getBreadcrumbItems()` does not drop anything itself — it emits that
60+
* ancestor with a `name` and `url: undefined`. The drop happens locally, in
61+
* this file: the `if (typeof item.name !== 'string' || !item.url) continue;`
62+
* guard in the loop below skips it. Fumadocs attaches a folder's `index.mdx`
63+
* as that folder's `index` node only when the folder's `meta.json` does
64+
* **not** list `"index"` in `pages`; a folder that lists it reaches this walk
65+
* with a `name` and no `url`. #12352 and #13946 fixed every `meta.json` under
66+
* `content/docs` that did so — today's corpus has none — but the condition is
67+
* structural, not retired: any `meta.json` that lists `"index"` again
68+
* reproduces it.
6669
*
6770
* ⛔ The missing URL is deliberately **not** reconstructed here — that fence is
6871
* the reason #12352 was fixable at all. The folder's index page exists, is in the

‎content/docs/references/api/plugin-rest-api.mdx‎

Lines changed: 4 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -60,8 +60,8 @@ no plugin code at all.
6060
## TypeScript Usage
6161

6262
```typescript
63-
import { ErrorHandlingConfigSchema, HandlerStatusSchema, OpenApiGenerationConfigSchema, RequestValidationConfigSchema, ResponseEnvelopeConfigSchema, RestApiEndpointSchema, RestApiPluginConfigSchema, RestApiRouteCategory, RestApiRouteRegistrationSchema, RouteCoverageEntrySchema, RouteCoverageReportSchema, ValidationMode } from '@objectstack/spec/api';
64-
import type { ErrorHandlingConfig, HandlerStatus, OpenApiGenerationConfig, RequestValidationConfig, ResponseEnvelopeConfig, RestApiEndpoint, RestApiPluginConfig, RestApiRouteCategory, RestApiRouteRegistration, RouteCoverageEntry, RouteCoverageReport, ValidationMode } from '@objectstack/spec/api';
63+
import { ErrorHandlingConfigSchema, OpenApiGenerationConfigSchema, RequestValidationConfigSchema, ResponseEnvelopeConfigSchema, RestApiEndpointSchema, RestApiPluginConfigSchema, RestApiRouteCategory, RestApiRouteRegistrationSchema, ValidationMode } from '@objectstack/spec/api';
64+
import type { ErrorHandlingConfig, OpenApiGenerationConfig, RequestValidationConfig, ResponseEnvelopeConfig, RestApiEndpoint, RestApiPluginConfig, RestApiRouteCategory, RestApiRouteRegistration, ValidationMode } from '@objectstack/spec/api';
6565

6666
// Validate data
6767
const result = ErrorHandlingConfigSchema.parse(data);
@@ -87,17 +87,6 @@ const result = ErrorHandlingConfigSchema.parse(data);
8787
| **redactFields** | `string[]` | optional | Field names to redact from error details |
8888

8989

90-
---
91-
92-
## HandlerStatus
93-
94-
### Allowed Values
95-
96-
* `implemented`
97-
* `stub`
98-
* `planned`
99-
100-
10190
---
10291

10392
## OpenApiGenerationConfig
@@ -197,7 +186,7 @@ const result = ErrorHandlingConfigSchema.parse(data);
197186
| **rateLimit** | `string` | optional | Rate limit policy name |
198187
| **cacheable** | `boolean` | optional (default: `false`) | Whether response can be cached |
199188
| **cacheTtl** | `integer` | optional | Cache TTL in seconds |
200-
| **handlerStatus** | `Enum<'implemented' \| 'stub' \| 'planned'>` | optional | Handler implementation status: implemented (default if omitted), stub, or planned |
189+
| **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). |
201190

202191

203192
---
@@ -372,7 +361,7 @@ const result = ErrorHandlingConfigSchema.parse(data);
372361
| **rateLimit** | `string` | optional | Rate limit policy name |
373362
| **cacheable** | `boolean` | optional (default: `false`) | Whether response can be cached |
374363
| **cacheTtl** | `integer` | optional | Cache TTL in seconds |
375-
| **handlerStatus** | `Enum<'implemented' \| 'stub' \| 'planned'>` | optional | Handler implementation status: implemented (default if omitted), stub, or planned |
364+
| **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). |
376365

377366
### Nested Shape: `RestApiRouteRegistration.middleware[number]`
378367

@@ -394,56 +383,6 @@ const result = ErrorHandlingConfigSchema.parse(data);
394383
| **tags** | `string[]` | optional | OpenAPI tags |
395384

396385

397-
---
398-
399-
## RouteCoverageEntry
400-
401-
### Properties
402-
403-
| Property | Type | Required | Description |
404-
| :--- | :--- | :--- | :--- |
405-
| **path** | `string` | ✅ | Full URL path (e.g. /api/v1/analytics/query) |
406-
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` | ✅ | HTTP method (GET, POST, etc.) |
407-
| **category** | `Enum<'discovery' \| 'metadata' \| 'data' \| 'batch' \| 'permission' \| 'analytics' \| 'automation' \| 'ui' \| 'realtime' \| 'notification' \| 'ai' \| 'i18n'>` | ✅ | Route category |
408-
| **handlerStatus** | `Enum<'implemented' \| 'stub' \| 'planned'>` | ✅ | Handler status |
409-
| **service** | `string` | ✅ | Target service name |
410-
| **healthCheckPassed** | `boolean` | optional | Whether the health check probe succeeded |
411-
412-
413-
---
414-
415-
## RouteCoverageReport
416-
417-
### Properties
418-
419-
| Property | Type | Required | Description |
420-
| :--- | :--- | :--- | :--- |
421-
| **timestamp** | `string` | ✅ | ISO 8601 timestamp |
422-
| **adapter** | `string` | ✅ | Adapter name (e.g. "hono", "express", "nextjs") |
423-
| **summary** | `{ total: integer; implemented: integer; stub: integer; planned: integer }` | ✅ | |
424-
| **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 |
425-
426-
### Nested Shape: `RouteCoverageReport.summary`
427-
428-
| Property | Type | Required | Description |
429-
| :--- | :--- | :--- | :--- |
430-
| **total** | `integer` | ✅ | Total declared endpoints |
431-
| **implemented** | `integer` | ✅ | Endpoints with real handlers |
432-
| **stub** | `integer` | ✅ | Endpoints with stub handlers (501) |
433-
| **planned** | `integer` | ✅ | Endpoints not yet implemented |
434-
435-
### Nested Shape: `RouteCoverageReport.entries[number]`
436-
437-
| Property | Type | Required | Description |
438-
| :--- | :--- | :--- | :--- |
439-
| **path** | `string` | ✅ | Full URL path (e.g. /api/v1/analytics/query) |
440-
| **method** | `Enum<'GET' \| 'POST' \| 'PUT' \| 'DELETE' \| 'PATCH' \| 'HEAD' \| 'OPTIONS'>` | ✅ | HTTP method (GET, POST, etc.) |
441-
| **category** | `Enum<'discovery' \| 'metadata' \| 'data' \| 'batch' \| 'permission' \| 'analytics' \| …>` | ✅ | Route category |
442-
| **handlerStatus** | `Enum<'implemented' \| 'stub' \| 'planned'>` | ✅ | Handler status |
443-
| **service** | `string` | ✅ | Target service name |
444-
| **healthCheckPassed** | `boolean` | optional | Whether the health check probe succeeded |
445-
446-
447386
---
448387

449388
## ValidationMode

0 commit comments

Comments
 (0)