Skip to content

Commit b529574

Browse files
Copilothotlong
andauthored
Changes before error encountered
Agent-Logs-Url: https://github.com/objectstack-ai/spec/sessions/b4c61b6c-9174-48d1-8cf4-65b626f3c81c Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 56d2f96 commit b529574

6 files changed

Lines changed: 36 additions & 16 deletions

File tree

apps/studio/src/hooks/use-api-discovery.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,7 @@ export function useApiDiscovery() {
233233

234234
// Only include services that are both enabled and have a handler ready.
235235
// Backwards-compatible: if handlerReady is not present (older backends),
236-
// fall back to status !== 'unavailable' && status !== 'stub' && status !== 'registered'.
236+
// treat status === 'available' or status === 'degraded' as equivalent to handlerReady: true.
237237
const isEnabled = serviceInfo?.enabled ?? false;
238238
const hasHandler = serviceInfo?.handlerReady
239239
?? (serviceInfo?.status === 'available' || serviceInfo?.status === 'degraded');

packages/runtime/src/dispatcher-plugin.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ function sendResult(result: HttpDispatcherResult, res: any): void {
4040
message: 'Not Found',
4141
code: 404,
4242
type: 'ROUTE_NOT_FOUND',
43-
hint: 'No handler matched this request. Check GET /api/v1/discovery for available routes.',
43+
hint: 'No handler matched this request. Check the API discovery endpoint for available routes.',
4444
},
4545
});
4646
}

packages/runtime/src/http-dispatcher.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -152,9 +152,9 @@ export class HttpDispatcher {
152152
};
153153

154154
// Build per-service status map
155-
// handlerReady: true means the dispatcher has a real handler for this route.
156-
// handlerReady: false with status 'registered' means the route is in the table
157-
// but the handler may not exist or be a stub.
155+
// handlerReady: true means the dispatcher has a real, bound handler for this route.
156+
// handlerReady: false means the route is present in the discovery table but may not
157+
// yet have a concrete implementation or may be served by a stub.
158158
const svcAvailable = (route?: string, provider?: string) => ({
159159
enabled: true, status: 'available' as const, handlerReady: true, route, provider,
160160
});

packages/spec/src/api/discovery.zod.ts

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
22

33
import { z } from 'zod';
4+
import { HttpMethod } from '../shared/http.zod';
45

56
/**
67
* Service Status Enum
@@ -37,16 +38,19 @@ export const ServiceInfoSchema = z.object({
3738
status: ServiceStatus,
3839
/**
3940
* Whether the HTTP handler for this service is confirmed to be mounted.
40-
* `true` = handler is registered in the adapter / dispatcher (safe to call).
41-
* `false` = route is declared (e.g., in Discovery metadata) but no handler exists
42-
* — requests will receive 501 Not Implemented.
4341
*
44-
* Clients SHOULD check this flag before displaying a service endpoint in the UI.
45-
* @default false
42+
* Semantics:
43+
* - `undefined` (omitted) = handler readiness is unknown / not yet verified.
44+
* - `true` = handler is registered in the adapter / dispatcher (safe to call).
45+
* - `false` = route is declared but no handler exists or only a stub is present
46+
* — requests are expected to receive 501 Not Implemented.
47+
*
48+
* Clients SHOULD check this flag before displaying or invoking a service endpoint and may
49+
* distinguish between "unknown" (omitted) and "known missing" (`false`).
4650
*/
4751
handlerReady: z.boolean().optional().describe(
4852
'Whether the HTTP handler is confirmed to be mounted. '
49-
+ 'Omitted or false means the route is declared but may return 501.'
53+
+ 'Omitted = readiness unknown/unverified; true = handler mounted; false = handler missing or stub (likely 501).'
5054
),
5155
/** Route path (only present if enabled) */
5256
route: z.string().optional().describe('e.g. /api/v1/analytics'),
@@ -226,7 +230,7 @@ export const RouteHealthEntrySchema = z.object({
226230
/** Route path (e.g. /api/v1/analytics) */
227231
route: z.string().describe('Route path pattern'),
228232
/** HTTP method */
229-
method: z.string().describe('HTTP method (GET, POST, etc.)'),
233+
method: HttpMethod.describe('HTTP method (GET, POST, etc.)'),
230234
/** Target service name */
231235
service: z.string().describe('Target service name'),
232236
/** Whether the route is declared in discovery */

packages/spec/src/api/dispatcher.zod.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -171,13 +171,17 @@ export const DEFAULT_DISPATCHER_ROUTES: DispatcherRouteInput[] = [
171171
/**
172172
* Semantic HTTP error codes used by the Dispatcher.
173173
*
174-
* The dispatcher MUST distinguish between these three failure modes so that
174+
* The dispatcher MUST distinguish between these four failure modes so that
175175
* clients (and developers) can understand *why* an API call failed:
176176
*
177177
* - `404` – Route Not Found: no route is registered for this path.
178178
* - `405` – Method Not Allowed: route exists but the HTTP method is not supported.
179179
* - `501` – Not Implemented: route is declared but the handler is a stub / not yet coded.
180180
* - `503` – Service Unavailable: service exists but is temporarily down or not loaded.
181+
*
182+
* Note: These are string representations of HTTP status codes for use in enum
183+
* matching. The `DispatcherErrorResponseSchema.error.code` field carries the
184+
* numeric HTTP status code for direct use in HTTP responses.
181185
*/
182186
export const DispatcherErrorCode = z.enum(['404', '405', '501', '503']).describe(
183187
'404 = route not found, 405 = method not allowed, 501 = not implemented (stub), 503 = service unavailable'

packages/spec/src/api/plugin-rest-api.zod.ts

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,18 @@ export type RestApiRouteCategory = z.infer<typeof RestApiRouteCategory>;
9494
// Route Registration Schema
9595
// ==========================================
9696

97+
/**
98+
* Handler Implementation Status
99+
* Shared enum for tracking whether an endpoint has a real handler.
100+
* Used by both `RestApiEndpointSchema` and `RouteCoverageEntrySchema`.
101+
*
102+
* - `implemented` – A real handler is coded and registered.
103+
* - `stub` – A placeholder handler exists that returns 501 Not Implemented.
104+
* - `planned` – Declared in the protocol spec but not yet implemented.
105+
*/
106+
export const HandlerStatusSchema = z.enum(['implemented', 'stub', 'planned']);
107+
export type HandlerStatus = z.infer<typeof HandlerStatusSchema>;
108+
97109
/**
98110
* REST API Endpoint Schema
99111
* Defines a single REST API endpoint with its metadata
@@ -169,7 +181,7 @@ export const RestApiEndpointSchema = z.object({
169181
* - `planned` – Declared in the protocol spec but not yet implemented.
170182
* @default 'implemented'
171183
*/
172-
handlerStatus: z.enum(['implemented', 'stub', 'planned']).optional()
184+
handlerStatus: HandlerStatusSchema.optional()
173185
.describe('Handler implementation status: implemented (default if omitted), stub, or planned'),
174186
});
175187

@@ -1685,11 +1697,11 @@ export const RouteCoverageEntrySchema = z.object({
16851697
/** Full URL path of the endpoint */
16861698
path: z.string().describe('Full URL path (e.g. /api/v1/analytics/query)'),
16871699
/** HTTP method */
1688-
method: z.string().describe('HTTP method (GET, POST, etc.)'),
1700+
method: HttpMethod.describe('HTTP method (GET, POST, etc.)'),
16891701
/** Route category */
16901702
category: RestApiRouteCategory.describe('Route category'),
16911703
/** Handler implementation status */
1692-
handlerStatus: z.enum(['implemented', 'stub', 'planned']).describe('Handler status'),
1704+
handlerStatus: HandlerStatusSchema.describe('Handler status'),
16931705
/** Target service */
16941706
service: z.string().describe('Target service name'),
16951707
/** Whether the handler was successfully called during health check */

0 commit comments

Comments
 (0)