fix(signals): add missing source field to TaskSyncedEvent (#78)
TaskSyncedPayload in shared-types and ml/serving schemas both require source, but TaskSyncedEvent in bus.ts and the todoist publish call both omitted it — causing the JetStream consumer to nak every task.synced message on validation failure. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -56,7 +56,7 @@ describe('EventBus — delivery', () => {
|
|||||||
it('does not throw when publishing with no subscribers', () => {
|
it('does not throw when publishing with no subscribers', () => {
|
||||||
const b = makeBus();
|
const b = makeBus();
|
||||||
expect(() =>
|
expect(() =>
|
||||||
b.publish('signals.task.synced', { userId: 'u', count: 3, syncedAt: '' }),
|
b.publish('signals.task.synced', { userId: 'u', source: 'todoist', count: 3, syncedAt: '' }),
|
||||||
).not.toThrow();
|
).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ describe('EventBus — ring buffer / tail()', () => {
|
|||||||
it('tail() filters by subject prefix', () => {
|
it('tail() filters by subject prefix', () => {
|
||||||
const b = makeBus();
|
const b = makeBus();
|
||||||
b.publish('signals.tip.served', { userId: 'u', tipId: 't', policy: 'p', servedAt: '' });
|
b.publish('signals.tip.served', { userId: 'u', tipId: 't', policy: 'p', servedAt: '' });
|
||||||
b.publish('signals.task.synced', { userId: 'u', count: 1, syncedAt: '' });
|
b.publish('signals.task.synced', { userId: 'u', source: 'todoist', count: 1, syncedAt: '' });
|
||||||
|
|
||||||
const tipEvents = b.tail({ subject: 'signals.tip' });
|
const tipEvents = b.tail({ subject: 'signals.tip' });
|
||||||
expect(tipEvents.every((e) => e.subject.startsWith('signals.tip'))).toBe(true);
|
expect(tipEvents.every((e) => e.subject.startsWith('signals.tip'))).toBe(true);
|
||||||
@@ -178,7 +178,7 @@ describe('EventBus — onPublish hook (NATS bridge contract)', () => {
|
|||||||
const hook = vi.fn();
|
const hook = vi.fn();
|
||||||
b.onPublish(hook);
|
b.onPublish(hook);
|
||||||
|
|
||||||
const payload = { userId: 'u', count: 2, syncedAt: 'now' };
|
const payload = { userId: 'u', source: 'todoist', count: 2, syncedAt: 'now' };
|
||||||
b.publish('signals.task.synced', payload);
|
b.publish('signals.task.synced', payload);
|
||||||
|
|
||||||
expect(hook).toHaveBeenCalledOnce();
|
expect(hook).toHaveBeenCalledOnce();
|
||||||
@@ -191,7 +191,7 @@ describe('EventBus — onPublish hook (NATS bridge contract)', () => {
|
|||||||
b.onPublish(() => calls.push('a'));
|
b.onPublish(() => calls.push('a'));
|
||||||
b.onPublish(() => calls.push('b'));
|
b.onPublish(() => calls.push('b'));
|
||||||
|
|
||||||
b.publish('signals.task.synced', { userId: 'u', count: 0, syncedAt: '' });
|
b.publish('signals.task.synced', { userId: 'u', source: 'todoist', count: 0, syncedAt: '' });
|
||||||
expect(calls).toEqual(['a', 'b']);
|
expect(calls).toEqual(['a', 'b']);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -202,7 +202,7 @@ describe('EventBus — onPublish hook (NATS bridge contract)', () => {
|
|||||||
b.onPublish(hook);
|
b.onPublish(hook);
|
||||||
b.subscribe('signals.task.synced', sub);
|
b.subscribe('signals.task.synced', sub);
|
||||||
|
|
||||||
b.publish('signals.task.synced', { userId: 'u', count: 1, syncedAt: '' });
|
b.publish('signals.task.synced', { userId: 'u', source: 'todoist', count: 1, syncedAt: '' });
|
||||||
expect(hook).toHaveBeenCalledOnce();
|
expect(hook).toHaveBeenCalledOnce();
|
||||||
expect(sub).toHaveBeenCalledOnce();
|
expect(sub).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
@@ -215,7 +215,7 @@ describe('EventBus — onPublish hook (NATS bridge contract)', () => {
|
|||||||
throw new Error('boom');
|
throw new Error('boom');
|
||||||
});
|
});
|
||||||
expect(() =>
|
expect(() =>
|
||||||
b.publish('signals.task.synced', { userId: 'u', count: 0, syncedAt: '' }),
|
b.publish('signals.task.synced', { userId: 'u', source: 'todoist', count: 0, syncedAt: '' }),
|
||||||
).toThrow('boom');
|
).toThrow('boom');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -106,7 +106,7 @@ describe('connectNats — bridge bus → JetStream', () => {
|
|||||||
|
|
||||||
await connectNats('nats://test:4222');
|
await connectNats('nats://test:4222');
|
||||||
|
|
||||||
const payload = { userId: 'u1', count: 7, syncedAt: '2026-01-01T00:00:00Z' };
|
const payload = { userId: 'u1', source: 'todoist', count: 7, syncedAt: '2026-01-01T00:00:00Z' };
|
||||||
bus.publish('signals.task.synced', payload);
|
bus.publish('signals.task.synced', payload);
|
||||||
|
|
||||||
// Allow the queued microtask in the hook to flush.
|
// Allow the queued microtask in the hook to flush.
|
||||||
@@ -130,7 +130,7 @@ describe('connectNats — bridge bus → JetStream', () => {
|
|||||||
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
const errSpy = vi.spyOn(console, 'error').mockImplementation(() => {});
|
||||||
|
|
||||||
expect(() =>
|
expect(() =>
|
||||||
bus.publish('signals.task.synced', { userId: 'u', count: 0, syncedAt: '' }),
|
bus.publish('signals.task.synced', { userId: 'u', source: 'todoist', count: 0, syncedAt: '' }),
|
||||||
).not.toThrow();
|
).not.toThrow();
|
||||||
|
|
||||||
// Wait a tick for the rejected promise's catch to run.
|
// Wait a tick for the rejected promise's catch to run.
|
||||||
@@ -156,7 +156,7 @@ describe('Bus.onPublish contract — used by NATS bridge', () => {
|
|||||||
const b = new Bus();
|
const b = new Bus();
|
||||||
const hook = vi.fn();
|
const hook = vi.fn();
|
||||||
b.onPublish(hook);
|
b.onPublish(hook);
|
||||||
b.publish('signals.task.synced', { userId: 'u', count: 0, syncedAt: '' });
|
b.publish('signals.task.synced', { userId: 'u', source: 'todoist', count: 0, syncedAt: '' });
|
||||||
expect(hook).toHaveBeenCalledOnce();
|
expect(hook).toHaveBeenCalledOnce();
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ export type RewardDeliveryFailedEvent = {
|
|||||||
|
|
||||||
export type TaskSyncedEvent = {
|
export type TaskSyncedEvent = {
|
||||||
userId: string;
|
userId: string;
|
||||||
|
source: string; // e.g. 'todoist'
|
||||||
count: number;
|
count: number;
|
||||||
syncedAt: string;
|
syncedAt: string;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ export class TodoistSignalSource implements SignalSource {
|
|||||||
});
|
});
|
||||||
|
|
||||||
this.cache.set(userId, { signals, fetchedAt: Date.now() });
|
this.cache.set(userId, { signals, fetchedAt: Date.now() });
|
||||||
bus.publish('signals.task.synced', { userId, count: signals.length, syncedAt: now });
|
bus.publish('signals.task.synced', { userId, source: 'todoist', count: signals.length, syncedAt: now });
|
||||||
|
|
||||||
return signals;
|
return signals;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user