feat(vibe): plan musical arcs and callbacks
This commit is contained in:
@@ -68,6 +68,23 @@ describe('durable Vibe session routes', () => {
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('reserves track_served for the authoritative /next operation', async () => {
|
||||
const { app, coordinator } = await appWithCoordinator();
|
||||
const result = await app.inject({
|
||||
method: 'POST', url: `/v2/vibe/sessions/${SESSION_ID}/events`,
|
||||
payload: {
|
||||
type: 'track_served',
|
||||
trackId: TRACK_ID,
|
||||
payload: { planVersionId: '33333333-3333-4333-8333-333333333333', ordinal: 0 },
|
||||
},
|
||||
});
|
||||
|
||||
expect(result.statusCode).toBe(400);
|
||||
expect(result.json()).toEqual({ error: 'track_served is reserved for the server /next operation' });
|
||||
expect(coordinator.appendEvent).not.toHaveBeenCalled();
|
||||
await app.close();
|
||||
});
|
||||
|
||||
it('returns a lifecycle conflict when an initial plan race ends or replaces the session', async () => {
|
||||
const { app, coordinator } = await appWithCoordinator();
|
||||
coordinator.start.mockRejectedValueOnce(
|
||||
|
||||
@@ -101,6 +101,12 @@ export default async function vibeSessionsRoutes(
|
||||
if (!body || !VIBE_EVENT_TYPES.includes(body.type as typeof VIBE_EVENT_TYPES[number])) {
|
||||
return reply.code(400).send({ error: 'type must be a supported Vibe event type' });
|
||||
}
|
||||
// Delivery is an authoritative state transition performed only by /next.
|
||||
// Accepting this event from the public ledger endpoint would let a client
|
||||
// fabricate exposure rows and consume the server-side surprise budget.
|
||||
if (body.type === 'track_served') {
|
||||
return reply.code(400).send({ error: 'track_served is reserved for the server /next operation' });
|
||||
}
|
||||
if (body.eventId !== undefined && !validUuid(body.eventId)) {
|
||||
return reply.code(400).send({ error: 'eventId must be a UUID' });
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user