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', () => {
|
||||
const b = makeBus();
|
||||
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();
|
||||
});
|
||||
|
||||
@@ -101,7 +101,7 @@ describe('EventBus — ring buffer / tail()', () => {
|
||||
it('tail() filters by subject prefix', () => {
|
||||
const b = makeBus();
|
||||
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' });
|
||||
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();
|
||||
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);
|
||||
|
||||
expect(hook).toHaveBeenCalledOnce();
|
||||
@@ -191,7 +191,7 @@ describe('EventBus — onPublish hook (NATS bridge contract)', () => {
|
||||
b.onPublish(() => calls.push('a'));
|
||||
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']);
|
||||
});
|
||||
|
||||
@@ -202,7 +202,7 @@ describe('EventBus — onPublish hook (NATS bridge contract)', () => {
|
||||
b.onPublish(hook);
|
||||
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(sub).toHaveBeenCalledOnce();
|
||||
});
|
||||
@@ -215,7 +215,7 @@ describe('EventBus — onPublish hook (NATS bridge contract)', () => {
|
||||
throw new Error('boom');
|
||||
});
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@@ -106,7 +106,7 @@ describe('connectNats — bridge bus → JetStream', () => {
|
||||
|
||||
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);
|
||||
|
||||
// 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(() => {});
|
||||
|
||||
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();
|
||||
|
||||
// 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 hook = vi.fn();
|
||||
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();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -45,6 +45,7 @@ export type RewardDeliveryFailedEvent = {
|
||||
|
||||
export type TaskSyncedEvent = {
|
||||
userId: string;
|
||||
source: string; // e.g. 'todoist'
|
||||
count: number;
|
||||
syncedAt: string;
|
||||
};
|
||||
|
||||
@@ -88,7 +88,7 @@ export class TodoistSignalSource implements SignalSource {
|
||||
});
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user