@@ -1505,6 +1505,149 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => {
15051505 } ) ,
15061506 ) ;
15071507
1508+ it . effect ( "clears stale pending approvals from projected shell summaries" , ( ) =>
1509+ Effect . gen ( function * ( ) {
1510+ const projectionPipeline = yield * OrchestrationProjectionPipeline ;
1511+ const eventStore = yield * OrchestrationEventStore ;
1512+ const sql = yield * SqlClient . SqlClient ;
1513+ const appendAndProject = ( event : Parameters < typeof eventStore . append > [ 0 ] ) =>
1514+ eventStore
1515+ . append ( event )
1516+ . pipe ( Effect . flatMap ( ( savedEvent ) => projectionPipeline . projectEvent ( savedEvent ) ) ) ;
1517+
1518+ yield * appendAndProject ( {
1519+ type : "project.created" ,
1520+ eventId : EventId . make ( "evt-stale-approval-1" ) ,
1521+ aggregateKind : "project" ,
1522+ aggregateId : ProjectId . make ( "project-stale-approval" ) ,
1523+ occurredAt : "2026-02-26T12:30:00.000Z" ,
1524+ commandId : CommandId . make ( "cmd-stale-approval-1" ) ,
1525+ causationEventId : null ,
1526+ correlationId : CorrelationId . make ( "cmd-stale-approval-1" ) ,
1527+ metadata : { } ,
1528+ payload : {
1529+ projectId : ProjectId . make ( "project-stale-approval" ) ,
1530+ title : "Project Stale Approval" ,
1531+ workspaceRoot : "/tmp/project-stale-approval" ,
1532+ defaultModelSelection : null ,
1533+ scripts : [ ] ,
1534+ createdAt : "2026-02-26T12:30:00.000Z" ,
1535+ updatedAt : "2026-02-26T12:30:00.000Z" ,
1536+ } ,
1537+ } ) ;
1538+
1539+ yield * appendAndProject ( {
1540+ type : "thread.created" ,
1541+ eventId : EventId . make ( "evt-stale-approval-2" ) ,
1542+ aggregateKind : "thread" ,
1543+ aggregateId : ThreadId . make ( "thread-stale-approval" ) ,
1544+ occurredAt : "2026-02-26T12:30:01.000Z" ,
1545+ commandId : CommandId . make ( "cmd-stale-approval-2" ) ,
1546+ causationEventId : null ,
1547+ correlationId : CorrelationId . make ( "cmd-stale-approval-2" ) ,
1548+ metadata : { } ,
1549+ payload : {
1550+ threadId : ThreadId . make ( "thread-stale-approval" ) ,
1551+ projectId : ProjectId . make ( "project-stale-approval" ) ,
1552+ title : "Thread Stale Approval" ,
1553+ modelSelection : {
1554+ provider : "codex" ,
1555+ model : "gpt-5-codex" ,
1556+ } ,
1557+ runtimeMode : "approval-required" ,
1558+ interactionMode : "default" ,
1559+ branch : null ,
1560+ worktreePath : null ,
1561+ createdAt : "2026-02-26T12:30:01.000Z" ,
1562+ updatedAt : "2026-02-26T12:30:01.000Z" ,
1563+ } ,
1564+ } ) ;
1565+
1566+ yield * appendAndProject ( {
1567+ type : "thread.activity-appended" ,
1568+ eventId : EventId . make ( "evt-stale-approval-3" ) ,
1569+ aggregateKind : "thread" ,
1570+ aggregateId : ThreadId . make ( "thread-stale-approval" ) ,
1571+ occurredAt : "2026-02-26T12:30:02.000Z" ,
1572+ commandId : CommandId . make ( "cmd-stale-approval-3" ) ,
1573+ causationEventId : null ,
1574+ correlationId : CorrelationId . make ( "cmd-stale-approval-3" ) ,
1575+ metadata : { } ,
1576+ payload : {
1577+ threadId : ThreadId . make ( "thread-stale-approval" ) ,
1578+ activity : {
1579+ id : EventId . make ( "activity-stale-approval-requested" ) ,
1580+ tone : "approval" ,
1581+ kind : "approval.requested" ,
1582+ summary : "Command approval requested" ,
1583+ payload : {
1584+ requestId : "approval-request-stale-1" ,
1585+ requestKind : "command" ,
1586+ } ,
1587+ turnId : null ,
1588+ createdAt : "2026-02-26T12:30:02.000Z" ,
1589+ } ,
1590+ } ,
1591+ } ) ;
1592+
1593+ yield * appendAndProject ( {
1594+ type : "thread.activity-appended" ,
1595+ eventId : EventId . make ( "evt-stale-approval-4" ) ,
1596+ aggregateKind : "thread" ,
1597+ aggregateId : ThreadId . make ( "thread-stale-approval" ) ,
1598+ occurredAt : "2026-02-26T12:30:03.000Z" ,
1599+ commandId : CommandId . make ( "cmd-stale-approval-4" ) ,
1600+ causationEventId : null ,
1601+ correlationId : CorrelationId . make ( "cmd-stale-approval-4" ) ,
1602+ metadata : { } ,
1603+ payload : {
1604+ threadId : ThreadId . make ( "thread-stale-approval" ) ,
1605+ activity : {
1606+ id : EventId . make ( "activity-stale-approval-failed" ) ,
1607+ tone : "error" ,
1608+ kind : "provider.approval.respond.failed" ,
1609+ summary : "Provider approval response failed" ,
1610+ payload : {
1611+ requestId : "approval-request-stale-1" ,
1612+ detail : "Unknown pending permission request: approval-request-stale-1" ,
1613+ } ,
1614+ turnId : null ,
1615+ createdAt : "2026-02-26T12:30:03.000Z" ,
1616+ } ,
1617+ } ,
1618+ } ) ;
1619+
1620+ const approvalRows = yield * sql < {
1621+ readonly requestId : string ;
1622+ readonly status : string ;
1623+ readonly resolvedAt : string | null ;
1624+ } > `
1625+ SELECT
1626+ request_id AS "requestId",
1627+ status,
1628+ resolved_at AS "resolvedAt"
1629+ FROM projection_pending_approvals
1630+ WHERE request_id = 'approval-request-stale-1'
1631+ ` ;
1632+ assert . deepEqual ( approvalRows , [
1633+ {
1634+ requestId : "approval-request-stale-1" ,
1635+ status : "resolved" ,
1636+ resolvedAt : "2026-02-26T12:30:03.000Z" ,
1637+ } ,
1638+ ] ) ;
1639+
1640+ const threadRows = yield * sql < {
1641+ readonly pendingApprovalCount : number ;
1642+ } > `
1643+ SELECT pending_approval_count AS "pendingApprovalCount"
1644+ FROM projection_threads
1645+ WHERE thread_id = 'thread-stale-approval'
1646+ ` ;
1647+ assert . deepEqual ( threadRows , [ { pendingApprovalCount : 0 } ] ) ;
1648+ } ) ,
1649+ ) ;
1650+
15081651 it . effect ( "does not fallback-retain messages whose turnId is removed by revert" , ( ) =>
15091652 Effect . gen ( function * ( ) {
15101653 const projectionPipeline = yield * OrchestrationProjectionPipeline ;
0 commit comments