Skip to content

Commit 4638aaa

Browse files
os-zhuangclaude
andauthored
feat(spec)!: MetadataWatchEvent.type carries only the values the runtime emits (#4536) (#4545)
The enum declared six values but three of them — the raw chokidar vocabulary add/change/unlink — had zero producers (declared-but- unenforced, Prime Directive #10). Both event construction sites normalize before the event exists: - packages/metadata/src/node-metadata-manager.ts translates chokidar's add/change/unlink in the watcher callbacks (handleFileEvent accepts only 'added' | 'changed' | 'deleted') - packages/metadata/src/metadata-manager.ts normalizes repository ops (create/update/delete -> added/changed/deleted) so the raw values never reached the event surface and no consumer branches on them (three-repo scan on the parent issue; re-verified here: the only other raw-vocabulary hits are chokidar-level wiring in cli/dev.ts, metadata/plugin.ts and metadata-fs/repository.ts, which emit different types entirely). Changes: - system/metadata-persistence.zod.ts: MetadataWatchEventSchema.type narrows to z.enum(['added', 'changed', 'deleted']), with a comment pointing at the translation site - contracts/metadata-service.ts: subscribe? TSDoc (and the import-site comment) stop mixing the two vocabularies - system/metadata-persistence.test.ts: canonical three parse; new pin test asserts add/change/unlink are rejected - generated references docs regenerated (enum cell only); api-surface.json unchanged (no export added or removed) - changeset (major): breaking only for an external implementor constructing events with the raw values — emit the canonical three; readers may delete branches on the raw values (they were unreachable). No tombstone / ADR-0087 conversion: runtime event envelope, not authorable metadata (the #4411 route). Claude-Session: https://claude.ai/code/session_01M9uWvoEp9CoLzYjNExj9sL Co-authored-by: Claude <noreply@anthropic.com>
1 parent 6117f7b commit 4638aaa

5 files changed

Lines changed: 36 additions & 8 deletions

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/spec": major
3+
---
4+
5+
`MetadataWatchEvent.type` now carries only the values the runtime emits: the enum narrows FROM `'add' | 'change' | 'unlink' | 'added' | 'changed' | 'deleted'` TO `'added' | 'changed' | 'deleted'` (#4536, follow-up to #4411).
6+
7+
The three raw chokidar values had zero producers: both emit sites normalize before constructing the event — `packages/metadata/src/node-metadata-manager.ts` translates chokidar's `add`/`change`/`unlink` in the watcher callbacks (`handleFileEvent` accepts only the canonical three), and `packages/metadata/src/metadata-manager.ts` normalizes repository ops (`create`/`update`/`delete``added`/`changed`/`deleted`). Consumers parsing events therefore never received the raw values, and no runtime behavior changes.
8+
9+
- FROM: an external implementor could construct events typed `'add'`/`'change'`/`'unlink'` and readers had to (needlessly) branch on six values.
10+
- TO: an implementor constructing events with the raw values must emit `added`/`changed`/`deleted` instead; readers may delete any branches on `add`/`change`/`unlink` — they were unreachable.
11+
12+
No tombstone / ADR-0087 conversion: this is a runtime event envelope type, not authorable metadata — nothing parses it on a load path (the #4411 route).

content/docs/references/system/metadata-persistence.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,7 +353,7 @@ const result = MetadataCollectionInfo.parse(data);
353353

354354
| Property | Type | Required | Description |
355355
| :--- | :--- | :--- | :--- |
356-
| **type** | `Enum<'add' \| 'change' \| 'unlink' \| 'added' \| 'changed' \| 'deleted'>` || |
356+
| **type** | `Enum<'added' \| 'changed' \| 'deleted'>` || |
357357
| **path** | `string` || |
358358
| **name** | `string` | optional | |
359359
| **stats** | `{ path?: string; size?: number; mtime?: string; hash?: string; … }` | optional | |

packages/spec/src/contracts/metadata-service.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
*/
3737

3838
import type { MetadataQuery, MetadataQueryResult, MetadataValidationResult, MetadataBulkResult, MetadataDependency } from '../kernel/metadata-plugin.zod';
39-
// The PERSISTENCE-side watch event (`add`/`added`/`changed`/`deleted`/…, path +
39+
// The PERSISTENCE-side watch event (`added`/`changed`/`deleted`, path +
4040
// file stats) — what `MetadataManager.subscribe` relays, as opposed to the
4141
// registration-level events `watch` forwards (`MetadataWatchCallback` below).
4242
// Spec used to carry a second, differently-shaped `MetadataWatchEvent` on
@@ -423,7 +423,7 @@ export interface IMetadataService {
423423
* NOT {@link watch} with a different return shape: the two carry different
424424
* events. `watch` reports registration-level transitions
425425
* (`registered`/`updated`/`unregistered`); `subscribe` relays the loader
426-
* pipeline's {@link MetadataWatchEvent} (`add`/`changed`/`deleted`, with
426+
* pipeline's {@link MetadataWatchEvent} (`added`/`changed`/`deleted`, with
427427
* path and file stats) — the granularity ObjectQLPlugin's metadata bridge
428428
* re-syncs runtime-authored hooks/actions from. (The first draft of this
429429
* member reused `watch`'s callback type; `MetadataManager implements

packages/spec/src/system/metadata-persistence.test.ts

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -381,16 +381,26 @@ describe('MetadataSaveResultSchema', () => {
381381
});
382382

383383
describe('MetadataWatchEventSchema', () => {
384-
it('should accept valid event types', () => {
385-
const types = ['add', 'change', 'unlink', 'added', 'changed', 'deleted'];
384+
it('should accept the canonical event types', () => {
385+
const types = ['added', 'changed', 'deleted'];
386386
types.forEach((type) => {
387387
expect(() => MetadataWatchEventSchema.parse({ type, path: '/test' })).not.toThrow();
388388
});
389389
});
390390

391+
// Pin (#4536): the raw chokidar vocabulary is translated in
392+
// NodeMetadataManager's watcher callbacks and never reaches the event
393+
// surface — the schema must reject it, not smuggle it back in.
394+
it('should reject the raw chokidar event types', () => {
395+
const rawTypes = ['add', 'change', 'unlink'];
396+
rawTypes.forEach((type) => {
397+
expect(() => MetadataWatchEventSchema.parse({ type, path: '/test' })).toThrow();
398+
});
399+
});
400+
391401
it('should accept full event', () => {
392402
const event = MetadataWatchEventSchema.parse({
393-
type: 'change',
403+
type: 'changed',
394404
path: '/metadata/view.json',
395405
name: 'account_view',
396406
stats: { size: 512 },
@@ -408,7 +418,7 @@ describe('MetadataWatchEventSchema', () => {
408418
});
409419

410420
it('should reject missing path', () => {
411-
expect(() => MetadataWatchEventSchema.parse({ type: 'add' })).toThrow();
421+
expect(() => MetadataWatchEventSchema.parse({ type: 'added' })).toThrow();
412422
});
413423
});
414424

packages/spec/src/system/metadata-persistence.zod.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -275,9 +275,15 @@ export const MetadataSaveResultSchema = lazySchema(() => z.object({
275275

276276
/**
277277
* Metadata Watch Event
278+
*
279+
* `type` carries only the values the runtime emits. The raw chokidar
280+
* vocabulary (`add`/`change`/`unlink`) is translated in NodeMetadataManager's
281+
* watcher callbacks (`packages/metadata/src/node-metadata-manager.ts`
282+
* `handleFileEvent`) and never reaches the event surface — the raw values had
283+
* zero producers when they were declared here (#4536, follow-up to #4411).
278284
*/
279285
export const MetadataWatchEventSchema = lazySchema(() => z.object({
280-
type: z.enum(['add', 'change', 'unlink', 'added', 'changed', 'deleted']),
286+
type: z.enum(['added', 'changed', 'deleted']),
281287
path: z.string(),
282288
name: z.string().optional(),
283289
stats: MetadataStatsSchema.optional(),

0 commit comments

Comments
 (0)