Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions .changeset/retire-default-dispatcher-routes.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
---
"@objectstack/spec": minor
---

feat(spec)!: delete `DEFAULT_DISPATCHER_ROUTES` — the dead route table that
underwrote a false compliance verdict (#3586, #3563 follow-up)

The const was consumed by nothing in the runtime — only its own tests and
`api-surface.json`. It listed dispatcher branches that never existed
(`/workflow`, `/realtime`) while omitting eight real prefixes (`/keys`,
`/mcp`, `/mcp/skill`, `/actions`, `/security`, `/share-links`, `/ready`,
`/openapi.json`), and `CLIENT_SPEC_COMPLIANCE.md` anchored a "FULLY
COMPLIANT" verdict on it while 27 real routes had no SDK expression.

The audited, guard-enforced source of truth for the dispatcher's route
surface is `packages/runtime/src/route-ledger.ts` (#3569): the conformance
suite fails when the registry and the ledger drift, which the dead table
never could.

Also swept the last GraphQL fixture debris that #3562's surface removal
left behind: registry test fixtures renamed to honest OData naming, the
tautological `config.graphql` assertions dropped, and the stale
`"type": "graphql"` JSDoc example in `registry.zod.ts` corrected.

Breaking for anyone importing `DEFAULT_DISPATCHER_ROUTES` (a repo-wide and
objectui-wide grep shows zero consumers); shipped as minor per the
launch-window convention, cf. #3562/#3581.
1 change: 0 additions & 1 deletion packages/spec/api-surface.json
Original file line number Diff line number Diff line change
Expand Up @@ -2378,7 +2378,6 @@
"DEFAULT_BATCH_ROUTES (const)",
"DEFAULT_DATA_CRUD_ROUTES (const)",
"DEFAULT_DISCOVERY_ROUTES (const)",
"DEFAULT_DISPATCHER_ROUTES (const)",
"DEFAULT_I18N_ROUTES (const)",
"DEFAULT_METADATA_ROUTES (const)",
"DEFAULT_NOTIFICATION_ROUTES (const)",
Expand Down
69 changes: 3 additions & 66 deletions packages/spec/src/api/dispatcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import {
DispatcherConfigSchema,
DispatcherErrorCode,
DispatcherErrorResponseSchema,
DEFAULT_DISPATCHER_ROUTES,
type DispatcherRoute,
type DispatcherConfig,
} from './dispatcher.zod';
Expand Down Expand Up @@ -120,71 +119,9 @@ describe('DispatcherConfigSchema', () => {
});
});

describe('DEFAULT_DISPATCHER_ROUTES', () => {
it('should have routes for all protocol namespaces', () => {
expect(DEFAULT_DISPATCHER_ROUTES.length).toBeGreaterThanOrEqual(15);
});

it('should include required services', () => {
const services = DEFAULT_DISPATCHER_ROUTES.map(r => r.service);
expect(services).toContain('metadata');
expect(services).toContain('data');
expect(services).toContain('auth');
});

it('should include the storage service', () => {
const services = DEFAULT_DISPATCHER_ROUTES.map(r => r.service);
expect(services).toContain('file-storage');
// The feed route was retired (ADR-0052 §5 / #3180) — comments/activity are
// served by the generic data API on sys_comment / sys_activity.
expect(DEFAULT_DISPATCHER_ROUTES.find(r => r.prefix.includes('feed'))).toBeUndefined();
});

it('should include optional services', () => {
const services = DEFAULT_DISPATCHER_ROUTES.map(r => r.service);
expect(services).toContain('ai');
expect(services).toContain('i18n');
expect(services).toContain('ui');
expect(services).toContain('workflow');
expect(services).toContain('realtime');
expect(services).toContain('notification');
expect(services).toContain('analytics');
expect(services).toContain('automation');
});

it('should have discovery as public route', () => {
const discovery = DEFAULT_DISPATCHER_ROUTES.find(r => r.prefix.includes('discovery'));
expect(discovery).toBeDefined();
expect(discovery!.authRequired).toBe(false);
});

it('should mark required services with required criticality', () => {
const required = DEFAULT_DISPATCHER_ROUTES.filter(r => r.criticality === 'required');
const requiredServices = required.map(r => r.service);
expect(requiredServices).toContain('metadata');
expect(requiredServices).toContain('data');
expect(requiredServices).toContain('auth');
});

it('should have all prefixes starting with /', () => {
DEFAULT_DISPATCHER_ROUTES.forEach(route => {
expect(route.prefix).toMatch(/^\//);
});
});

it('should be parseable by DispatcherConfigSchema', () => {
expect(() => DispatcherConfigSchema.parse({
routes: DEFAULT_DISPATCHER_ROUTES,
})).not.toThrow();
});

it('should include health route', () => {
const health = DEFAULT_DISPATCHER_ROUTES.find(r => r.prefix.includes('health'));
expect(health).toBeDefined();
expect(health!.authRequired).toBe(false);
expect(health!.criticality).toBe('required');
});
});
// DEFAULT_DISPATCHER_ROUTES tests removed with the const (#3586) — the
// dispatcher's real route surface is asserted by the route-ledger conformance
// suite in packages/runtime.

// ============================================================================
// Dispatcher Error Schemas
Expand Down
39 changes: 6 additions & 33 deletions packages/spec/src/api/dispatcher.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -128,40 +128,13 @@ export type DispatcherConfig = z.infer<typeof DispatcherConfigSchema>;
export type DispatcherConfigInput = z.input<typeof DispatcherConfigSchema>;

// ============================================================================
// Default Route Table
// Default Route Table — REMOVED (#3586)
// ============================================================================

/**
* Default route table for the ObjectStack HttpDispatcher.
* Maps all Protocol namespaces to their corresponding services.
*
* This is the recommended baseline configuration. Plugins can extend
* this table by declaring routes in their manifest's contributes.routes.
*/
export const DEFAULT_DISPATCHER_ROUTES: DispatcherRouteInput[] = [
// Discovery (public)
{ prefix: '/api/v1/discovery', service: 'metadata', authRequired: false, criticality: 'required' },

// Health (public)
{ prefix: '/api/v1/health', service: 'metadata', authRequired: false, criticality: 'required' },

// Required Services
{ prefix: '/api/v1/meta', service: 'metadata', criticality: 'required' },
{ prefix: '/api/v1/data', service: 'data', criticality: 'required' },
{ prefix: '/api/v1/auth', service: 'auth', criticality: 'required' },

// Optional Services (plugin-provided)
{ prefix: '/api/v1/packages', service: 'metadata' },
{ prefix: '/api/v1/ui', service: 'ui' }, // @deprecated — use /api/v1/meta/view and /api/v1/meta/dashboard instead
{ prefix: '/api/v1/workflow', service: 'workflow' },
{ prefix: '/api/v1/analytics', service: 'analytics' },
{ prefix: '/api/v1/automation', service: 'automation' },
{ prefix: '/api/v1/storage', service: 'file-storage' },
{ prefix: '/api/v1/i18n', service: 'i18n' },
{ prefix: '/api/v1/notifications', service: 'notification' },
{ prefix: '/api/v1/realtime', service: 'realtime' },
{ prefix: '/api/v1/ai', service: 'ai' },
];
// DEFAULT_DISPATCHER_ROUTES was deleted: nothing in the runtime ever consumed
// it, it listed routes that never existed (/workflow, /realtime) while
// omitting eight real ones, and it underwrote a false compliance verdict.
// The audited, guard-enforced source of truth for the dispatcher's route
// surface is packages/runtime/src/route-ledger.ts.

// ============================================================================
// Dispatcher Error Codes
Expand Down
2 changes: 0 additions & 2 deletions packages/spec/src/api/query-adapter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,6 @@ describe('QueryAdapterConfigSchema', () => {
const config = QueryAdapterConfigSchema.parse({});

expect(config.rest).toBeUndefined();
expect(config.graphql).toBeUndefined();
expect(config.odata).toBeUndefined();
});

Expand Down Expand Up @@ -177,7 +176,6 @@ describe('QueryAdapterConfigSchema', () => {
});

expect(config.rest?.filterStyle).toBe('rsql');
expect(config.graphql).toBeUndefined();
expect(config.odata).toBeUndefined();
});
});
16 changes: 8 additions & 8 deletions packages/spec/src/api/registry.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -414,15 +414,15 @@ describe('API Registry Protocol', () => {
],
},
{
id: 'graphql_api',
name: 'GraphQL API',
id: 'odata_api',
name: 'OData API',
type: 'odata',
version: 'v1',
basePath: '/graphql',
basePath: '/odata',
endpoints: [
{
id: 'graphql_query',
path: '/graphql',
id: 'odata_query',
path: '/odata',
responses: [],
},
],
Expand Down Expand Up @@ -459,11 +459,11 @@ describe('API Registry Protocol', () => {
endpoints: [],
},
{
id: 'graphql_api',
name: 'GraphQL API',
id: 'odata_api',
name: 'OData API',
type: 'odata' as const,
version: 'v1',
basePath: '/graphql',
basePath: '/odata',
endpoints: [],
},
],
Expand Down
2 changes: 1 addition & 1 deletion packages/spec/src/api/registry.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -680,7 +680,7 @@ export type ConflictResolutionStrategy = z.infer<typeof ConflictResolutionStrate
* "conflictResolution": "priority",
* "apis": [
* { "id": "customer_api", "type": "rest", ... },
* { "id": "graphql_api", "type": "graphql", ... },
* { "id": "odata_api", "type": "odata", ... },
* { "id": "file_upload_api", "type": "file", ... }
* ],
* "totalApis": 3,
Expand Down