Skip to content

Commit 8abf133

Browse files
os-zhuangclaude
andauthored
feat(spec)!: retire residual feed discovery/dispatcher surface (#3180) (#3204)
Follow-up to #1959 / ADR-0052 §5. The feed backend was retired long ago; this removes the last discovery/dispatcher references to it and fixes a real bug in the comments capability. - spec: remove WellKnownCapabilitiesSchema.feed and ApiRoutesSchema.feed (routes.feed); drop the /api/v1/feed entry from DEFAULT_DISPATCHER_ROUTES. - metadata-protocol: getDiscovery() no longer emits the always-false feed service/capability. Bug fix — the `comments` capability keyed off the deleted 'feed' service (permanently false since #1955); it now tracks the sys_comment object (always-on audit slate), so declared === enforced. - client: drop the internal feed route constant (only existed to satisfy the now-removed ApiRoutes.feed type; unused). - tests: update discovery/dispatcher/client capability tests; add positive coverage that comments=true when sys_comment is registered. Comments/activity are served by the generic data API on sys_comment / sys_activity. Regenerated content/docs/references/api/discovery.mdx. Claude-Session: https://claude.ai/code/session_01HtN5uPoPnFzkSJ2HcwzgtG Co-authored-by: Claude <noreply@anthropic.com>
1 parent 230358c commit 8abf133

10 files changed

Lines changed: 58 additions & 55 deletions

File tree

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
---
2+
"@objectstack/spec": minor
3+
"@objectstack/metadata-protocol": patch
4+
"@objectstack/client": patch
5+
---
6+
7+
**Breaking (discovery response shape): retire the residual feed capability surface (#3180, follow-up to #1959 / ADR-0052 §5).**
8+
9+
The feed backend was retired long ago; #1959 removed the feed contracts + SDK. This
10+
removes the last discovery/dispatcher references to it, and fixes a real bug where the
11+
`comments` capability was permanently `false`.
12+
13+
- `@objectstack/spec``WellKnownCapabilitiesSchema.feed` and `ApiRoutesSchema.feed`
14+
(`routes.feed`) are **removed**, and the `/api/v1/feed` entry is dropped from
15+
`DEFAULT_DISPATCHER_ROUTES`. FROM → TO: clients reading `discovery.capabilities.feed`
16+
or `discovery.routes.feed` → use `discovery.capabilities.comments`; comments/activity
17+
are served by the generic data API on `sys_comment` / `sys_activity`
18+
(`/api/v1/data/sys_comment/…`).
19+
- `@objectstack/metadata-protocol``getDiscovery()` no longer emits the always-`false`
20+
`feed` service/capability. **Bug fix:** the `comments` capability previously keyed off
21+
the deleted `'feed'` service (so it was permanently `false` after #1955); it now tracks
22+
the presence of the `sys_comment` object (provided by the always-on audit slate), so
23+
`declared === enforced`.
24+
- `@objectstack/client` — the internal `feed: '/api/v1/feed'` route constant is removed
25+
(it only existed to satisfy the now-removed `ApiRoutes.feed` type; no client code used it).

content/docs/references/api/discovery.mdx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,6 @@ const result = ApiRoutes.parse(data);
5959
| **notifications** | `string` | optional | e.g. /api/v1/notifications |
6060
| **ai** | `string` | optional | e.g. /api/v1/ai |
6161
| **i18n** | `string` | optional | e.g. /api/v1/i18n |
62-
| **feed** | `string` | optional | e.g. /api/v1/feed |
6362

6463

6564
---
@@ -155,8 +154,7 @@ Well-known capability flags for frontend intelligent adaptation
155154

156155
| Property | Type | Required | Description |
157156
| :--- | :--- | :--- | :--- |
158-
| **feed** | `boolean` || Whether the backend supports Feed / Chatter API |
159-
| **comments** | `boolean` || Whether the backend supports comments (a subset of Feed) |
157+
| **comments** | `boolean` || Whether the backend supports record comments / chatter (the `sys_comment` object served via the data API) |
160158
| **automation** | `boolean` || Whether the backend supports Automation CRUD (flows, triggers) |
161159
| **cron** | `boolean` || Whether the backend supports cron scheduling |
162160
| **search** | `boolean` || Whether the backend supports full-text search |

packages/client/src/client.test.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -863,7 +863,6 @@ describe('ObjectStackClient.automation', () => {
863863

864864
it('should expose capabilities after connect', async () => {
865865
const caps = {
866-
feed: true,
867866
comments: true,
868867
automation: false,
869868
cron: false,
@@ -887,7 +886,7 @@ describe('ObjectStackClient.automation', () => {
887886

888887
await client.connect();
889888
expect(client.capabilities).toBeDefined();
890-
expect(client.capabilities!.feed).toBe(true);
889+
expect(client.capabilities!.comments).toBe(true);
891890
expect(client.capabilities!.automation).toBe(false);
892891
expect(client.capabilities!.search).toBe(true);
893892
});

packages/client/src/index.ts

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3202,11 +3202,6 @@ export class ObjectStackClient {
32023202
notifications: '/api/v1/notifications',
32033203
ai: '/api/v1/ai',
32043204
i18n: '/api/v1/i18n',
3205-
// NOTE: the `feed` route constant is retained only to satisfy the
3206-
// discovery `ApiRoutes` type (`routes.feed`), which is a separate
3207-
// follow-up cleanup. The feed SDK accessor + backend were retired
3208-
// (ADR-0052 §5); no client code consumes this route.
3209-
feed: '/api/v1/feed',
32103205
graphql: '/graphql',
32113206
};
32123207

packages/metadata-protocol/src/protocol.ts

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,22 +1307,6 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
13071307
}
13081308
}
13091309

1310-
// Add feed service status
1311-
if (registeredServices.has('feed')) {
1312-
services['feed'] = {
1313-
enabled: true,
1314-
status: 'available' as const,
1315-
route: '/api/v1/data',
1316-
provider: 'service-feed',
1317-
};
1318-
} else {
1319-
services['feed'] = {
1320-
enabled: false,
1321-
status: 'unavailable' as const,
1322-
message: 'Install service-feed to enable',
1323-
};
1324-
}
1325-
13261310
const routes: ApiRoutes = {
13271311
data: '/api/v1/data',
13281312
metadata: '/api/v1/meta',
@@ -1333,8 +1317,11 @@ export class ObjectStackProtocolImplementation implements ObjectStackProtocol {
13331317
// DiscoverySchema defines capabilities as Record<string, { enabled, features?, description? }>
13341318
// (hierarchical format). We also keep a flat WellKnownCapabilities for backward compat.
13351319
const wellKnown: WellKnownCapabilities = {
1336-
feed: registeredServices.has('feed'),
1337-
comments: registeredServices.has('feed'),
1320+
// Comments/chatter are served by the `sys_comment` object via the generic
1321+
// data API (ADR-0052 §5) — not a dedicated service. The capability is true
1322+
// iff that object is loaded (the always-on audit slate provides it); this
1323+
// keeps declared === enforced (Prime Directive #10). #3180
1324+
comments: !!this.engine.registry?.getObject?.('sys_comment'),
13381325
automation: registeredServices.has('automation'),
13391326
cron: registeredServices.has('job'),
13401327
search: registeredServices.has('search'),

packages/objectql/src/protocol-discovery.test.ts

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ describe('ObjectStackProtocolImplementation - Dynamic Service Discovery', () =>
198198
// workflow is registered but doesn't map to a well-known capability directly
199199
expect(discovery.services.workflow.enabled).toBe(true);
200200
// All well-known capabilities should be disabled since workflow doesn't map to any
201-
expect(discovery.capabilities!.feed).toEqual({ enabled: false });
201+
// (comments derives from the sys_comment object, which is not registered here).
202202
expect(discovery.capabilities!.comments).toEqual({ enabled: false });
203203
expect(discovery.capabilities!.automation).toEqual({ enabled: false });
204204
expect(discovery.capabilities!.cron).toEqual({ enabled: false });
@@ -212,7 +212,6 @@ describe('ObjectStackProtocolImplementation - Dynamic Service Discovery', () =>
212212
const discovery = await protocol.getDiscovery();
213213

214214
expect(discovery.capabilities).toBeDefined();
215-
expect(discovery.capabilities!.feed).toEqual({ enabled: false });
216215
expect(discovery.capabilities!.comments).toEqual({ enabled: false });
217216
expect(discovery.capabilities!.automation).toEqual({ enabled: false });
218217
expect(discovery.capabilities!.cron).toEqual({ enabled: false });
@@ -223,21 +222,34 @@ describe('ObjectStackProtocolImplementation - Dynamic Service Discovery', () =>
223222

224223
it('should dynamically set capabilities based on registered services', async () => {
225224
const mockServices = new Map<string, any>();
226-
mockServices.set('feed', {});
227225
mockServices.set('automation', {});
228226
mockServices.set('search', {});
229227
mockServices.set('file-storage', {});
230228

231229
protocol = new ObjectStackProtocolImplementation(engine, () => mockServices);
232230
const discovery = await protocol.getDiscovery();
233231

234-
expect(discovery.capabilities!.feed).toEqual({ enabled: true });
235-
expect(discovery.capabilities!.comments).toEqual({ enabled: true });
236232
expect(discovery.capabilities!.automation).toEqual({ enabled: true });
237233
expect(discovery.capabilities!.cron).toEqual({ enabled: false });
238234
expect(discovery.capabilities!.search).toEqual({ enabled: true });
239235
expect(discovery.capabilities!.export).toEqual({ enabled: true });
240236
expect(discovery.capabilities!.chunkedUpload).toEqual({ enabled: true });
237+
// comments is independent of services — it tracks the sys_comment object (#3180).
238+
expect(discovery.capabilities!.comments).toEqual({ enabled: false });
239+
});
240+
241+
it('should enable comments capability when the sys_comment object is registered (#3180)', async () => {
242+
// comments/chatter are served by the sys_comment object via the data API
243+
// (ADR-0052 §5), not a dedicated service — so the capability tracks that
244+
// object's presence, keeping declared === enforced.
245+
engine.registerObject(
246+
{ name: 'sys_comment', label: 'Comment', fields: { body: { type: 'text' } } } as any,
247+
'plugin-audit',
248+
);
249+
protocol = new ObjectStackProtocolImplementation(engine, () => new Map());
250+
const discovery = await protocol.getDiscovery();
251+
252+
expect(discovery.capabilities!.comments).toEqual({ enabled: true });
241253
});
242254

243255
it('should enable cron capability when job service is registered', async () => {

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

Lines changed: 3 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,13 +37,11 @@ describe('ApiRoutesSchema', () => {
3737
actions: '/api/v1/p',
3838
storage: '/api/v1/storage',
3939
graphql: '/api/v1/graphql',
40-
feed: '/api/v1/feed',
4140
discovery: '/api/v1/discovery',
4241
});
4342

4443
expect(routes.data).toBe('/api/v1/data');
4544
expect(routes.graphql).toBe('/api/v1/graphql');
46-
expect(routes.feed).toBe('/api/v1/feed');
4745
expect(routes.discovery).toBe('/api/v1/discovery');
4846
});
4947

@@ -705,7 +703,6 @@ describe('DiscoverySchema (schemaDiscovery field)', () => {
705703
describe('WellKnownCapabilitiesSchema', () => {
706704
it('should accept all capabilities enabled', () => {
707705
const caps: WellKnownCapabilities = {
708-
feed: true,
709706
comments: true,
710707
automation: true,
711708
cron: true,
@@ -718,27 +715,25 @@ describe('WellKnownCapabilitiesSchema', () => {
718715

719716
it('should accept all capabilities disabled', () => {
720717
const caps = WellKnownCapabilitiesSchema.parse({
721-
feed: false,
722718
comments: false,
723719
automation: false,
724720
cron: false,
725721
search: false,
726722
export: false,
727723
chunkedUpload: false,
728724
});
729-
expect(caps.feed).toBe(false);
725+
expect(caps.comments).toBe(false);
730726
expect(caps.chunkedUpload).toBe(false);
731727
});
732728

733729
it('should reject missing required fields', () => {
734-
expect(() => WellKnownCapabilitiesSchema.parse({ feed: true })).toThrow();
730+
expect(() => WellKnownCapabilitiesSchema.parse({ comments: true })).toThrow();
735731
expect(() => WellKnownCapabilitiesSchema.parse({})).toThrow();
736732
});
737733

738734
it('should reject non-boolean values', () => {
739735
expect(() => WellKnownCapabilitiesSchema.parse({
740-
feed: 'yes',
741-
comments: true,
736+
comments: 'yes',
742737
automation: true,
743738
cron: true,
744739
search: true,
@@ -749,7 +744,6 @@ describe('WellKnownCapabilitiesSchema', () => {
749744

750745
it('should have .describe() annotations on all fields', () => {
751746
const shape = WellKnownCapabilitiesSchema.shape;
752-
expect(shape.feed.description).toBeDefined();
753747
expect(shape.comments.description).toBeDefined();
754748
expect(shape.automation.description).toBeDefined();
755749
expect(shape.cron.description).toBeDefined();

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

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -201,9 +201,6 @@ export const ApiRoutesSchema = lazySchema(() => z.object({
201201

202202
/** Base URL for Internationalization */
203203
i18n: z.string().optional().describe('e.g. /api/v1/i18n'),
204-
205-
/** Base URL for Feed / Chatter API */
206-
feed: z.string().optional().describe('e.g. /api/v1/feed'),
207204
}));
208205

209206
/**
@@ -280,10 +277,8 @@ export const DiscoverySchema = lazySchema(() => z.object({
280277
* Clients can use these to show/hide UI elements without probing individual endpoints.
281278
*/
282279
export const WellKnownCapabilitiesSchema = lazySchema(() => z.object({
283-
/** Whether the backend supports Feed / Chatter API */
284-
feed: z.boolean().describe('Whether the backend supports Feed / Chatter API'),
285-
/** Whether the backend supports comments (a subset of Feed) */
286-
comments: z.boolean().describe('Whether the backend supports comments (a subset of Feed)'),
280+
/** Whether the backend supports record comments / chatter (served by `sys_comment` via the data API) */
281+
comments: z.boolean().describe('Whether the backend supports record comments / chatter (the `sys_comment` object served via the data API)'),
287282
/** Whether the backend supports Automation CRUD (flows, triggers) */
288283
automation: z.boolean().describe('Whether the backend supports Automation CRUD (flows, triggers)'),
289284
/** Whether the backend supports cron scheduling */

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

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,12 @@ describe('DEFAULT_DISPATCHER_ROUTES', () => {
132132
expect(services).toContain('auth');
133133
});
134134

135-
it('should include storage and feed services', () => {
135+
it('should include the storage service', () => {
136136
const services = DEFAULT_DISPATCHER_ROUTES.map(r => r.service);
137137
expect(services).toContain('file-storage');
138-
// feed route maps to the data service
139-
const feedRoute = DEFAULT_DISPATCHER_ROUTES.find(r => r.prefix.includes('feed'));
140-
expect(feedRoute).toBeDefined();
141-
expect(feedRoute!.service).toBe('data');
138+
// The feed route was retired (ADR-0052 §5 / #3180) — comments/activity are
139+
// served by the generic data API on sys_comment / sys_activity.
140+
expect(DEFAULT_DISPATCHER_ROUTES.find(r => r.prefix.includes('feed'))).toBeUndefined();
142141
});
143142

144143
it('should include optional services', () => {

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -157,7 +157,6 @@ export const DEFAULT_DISPATCHER_ROUTES: DispatcherRouteInput[] = [
157157
{ prefix: '/api/v1/analytics', service: 'analytics' },
158158
{ prefix: '/api/v1/automation', service: 'automation' },
159159
{ prefix: '/api/v1/storage', service: 'file-storage' },
160-
{ prefix: '/api/v1/feed', service: 'data' },
161160
{ prefix: '/api/v1/i18n', service: 'i18n' },
162161
{ prefix: '/api/v1/notifications', service: 'notification' },
163162
{ prefix: '/api/v1/realtime', service: 'realtime' },

0 commit comments

Comments
 (0)