| ... | @@ -174,6 +174,78 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { | ... | @@ -174,6 +174,78 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => { |
| 174 | assert.equal(row.lastAppliedSequence, 3); | 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 | // Settled lifecycle through the DB pipeline: thread.settled writes the | 249 | // Settled lifecycle through the DB pipeline: thread.settled writes the |
| 178 | // override + timestamp, thread.unsettled(user) flips to the active pin. | 250 | // override + timestamp, thread.unsettled(user) flips to the active pin. |
| 179 | yield* eventStore.append({ | 251 | yield* eventStore.append({ |