| ... | ... | @@ -174,6 +174,78 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { |
| 174 | 174 | assert.equal(row.lastAppliedSequence, 3); |
| 175 | 175 | } |
| 176 | 176 | |
| 177 | yield* sql`CREATE TABLE thread_shell_updates (count INTEGER NOT NULL)`; |
| 178 | yield* sql`INSERT INTO thread_shell_updates (count) VALUES (0)`; |
| 179 | yield* sql` |
| 180 | CREATE TRIGGER count_thread_shell_updates |
| 181 | AFTER UPDATE ON projection_threads |
| 182 | WHEN NEW.thread_id = 'thread-1' |
| 183 | BEGIN |
| 184 | UPDATE thread_shell_updates SET count = count + 1; |
| 185 | END; |
| 186 | `; |
| 187 | |
| 188 | yield* eventStore.append({ |
| 189 | type: "thread.message-sent", |
| 190 | eventId: EventId.make("evt-assistant-update"), |
| 191 | aggregateKind: "thread", |
| 192 | aggregateId: ThreadId.make("thread-1"), |
| 193 | occurredAt: "2026-01-01T00:00:00.100Z", |
| 194 | commandId: CommandId.make("cmd-assistant-update"), |
| 195 | causationEventId: null, |
| 196 | correlationId: CommandId.make("cmd-assistant-update"), |
| 197 | metadata: {}, |
| 198 | payload: { |
| 199 | threadId: ThreadId.make("thread-1"), |
| 200 | messageId: MessageId.make("message-2"), |
| 201 | role: "assistant", |
| 202 | text: "more work", |
| 203 | turnId: null, |
| 204 | streaming: false, |
| 205 | createdAt: "2026-01-01T00:00:00.100Z", |
| 206 | updatedAt: "2026-01-01T00:00:00.100Z", |
| 207 | }, |
| 208 | }); |
| 209 | yield* projectionPipeline.bootstrap; |
| 210 | |
| 211 | let threadShellUpdates = yield* sql<{ readonly count: number }>` |
| 212 | SELECT count FROM thread_shell_updates |
| 213 | `; |
| 214 | assert.deepEqual(threadShellUpdates, [{ count: 1 }]); |
| 215 | |
| 216 | yield* sql`UPDATE thread_shell_updates SET count = 0`; |
| 217 | yield* eventStore.append({ |
| 218 | type: "thread.activity-appended", |
| 219 | eventId: EventId.make("evt-routine-activity"), |
| 220 | aggregateKind: "thread", |
| 221 | aggregateId: ThreadId.make("thread-1"), |
| 222 | occurredAt: "2026-01-01T00:00:00.200Z", |
| 223 | commandId: CommandId.make("cmd-routine-activity"), |
| 224 | causationEventId: null, |
| 225 | correlationId: CommandId.make("cmd-routine-activity"), |
| 226 | metadata: {}, |
| 227 | payload: { |
| 228 | threadId: ThreadId.make("thread-1"), |
| 229 | activity: { |
| 230 | id: EventId.make("activity-routine"), |
| 231 | tone: "tool", |
| 232 | kind: "tool.updated", |
| 233 | summary: "Tool made progress", |
| 234 | payload: {}, |
| 235 | turnId: null, |
| 236 | createdAt: "2026-01-01T00:00:00.200Z", |
| 237 | }, |
| 238 | }, |
| 239 | }); |
| 240 | yield* projectionPipeline.bootstrap; |
| 241 | |
| 242 | threadShellUpdates = yield* sql<{ readonly count: number }>` |
| 243 | SELECT count FROM thread_shell_updates |
| 244 | `; |
| 245 | assert.deepEqual(threadShellUpdates, [{ count: 1 }]); |
| 246 | yield* sql`DROP TRIGGER count_thread_shell_updates`; |
| 247 | yield* sql`DROP TABLE thread_shell_updates`; |
| 248 | |
| 177 | 249 | // Settled lifecycle through the DB pipeline: thread.settled writes the |
| 178 | 250 | // override + timestamp, thread.unsettled(user) flips to the active pin. |
| 179 | 251 | yield* eventStore.append({ |