Skip to content

Commit 33ef2dc

Browse files
authored
Merge pull request #60 from tyulyukov/marcode/port-data-integrity-migrations
feat(migrations): port data-integrity migrations with shell-summary backfill
2 parents 88dd714 + a3d309e commit 33ef2dc

14 files changed

Lines changed: 1338 additions & 5 deletions

CLAUDE.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
AGENTS.md
1+
AGENTS.md

apps/server/src/orchestration/Layers/ProjectionPipeline.test.ts

Lines changed: 316 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,322 @@ it.layer(BaseTestLayer)("OrchestrationProjectionPipeline", (it) => {
14991499
}),
15001500
);
15011501

1502+
it.effect("clears stale pending approvals from projected shell summaries", () =>
1503+
Effect.gen(function* () {
1504+
const projectionPipeline = yield* OrchestrationProjectionPipeline;
1505+
const eventStore = yield* OrchestrationEventStore;
1506+
const sql = yield* SqlClient.SqlClient;
1507+
const appendAndProject = (event: Parameters<typeof eventStore.append>[0]) =>
1508+
eventStore
1509+
.append(event)
1510+
.pipe(Effect.flatMap((savedEvent) => projectionPipeline.projectEvent(savedEvent)));
1511+
1512+
yield* appendAndProject({
1513+
type: "project.created",
1514+
eventId: EventId.make("evt-stale-approval-1"),
1515+
aggregateKind: "project",
1516+
aggregateId: ProjectId.make("project-stale-approval"),
1517+
occurredAt: "2026-02-26T12:30:00.000Z",
1518+
commandId: CommandId.make("cmd-stale-approval-1"),
1519+
causationEventId: null,
1520+
correlationId: CorrelationId.make("cmd-stale-approval-1"),
1521+
metadata: {},
1522+
payload: {
1523+
projectId: ProjectId.make("project-stale-approval"),
1524+
title: "Project Stale Approval",
1525+
workspaceRoot: "/tmp/project-stale-approval",
1526+
defaultModelSelection: null,
1527+
scripts: [],
1528+
createdAt: "2026-02-26T12:30:00.000Z",
1529+
updatedAt: "2026-02-26T12:30:00.000Z",
1530+
},
1531+
});
1532+
1533+
yield* appendAndProject({
1534+
type: "thread.created",
1535+
eventId: EventId.make("evt-stale-approval-2"),
1536+
aggregateKind: "thread",
1537+
aggregateId: ThreadId.make("thread-stale-approval"),
1538+
occurredAt: "2026-02-26T12:30:01.000Z",
1539+
commandId: CommandId.make("cmd-stale-approval-2"),
1540+
causationEventId: null,
1541+
correlationId: CorrelationId.make("cmd-stale-approval-2"),
1542+
metadata: {},
1543+
payload: {
1544+
threadId: ThreadId.make("thread-stale-approval"),
1545+
projectId: ProjectId.make("project-stale-approval"),
1546+
title: "Thread Stale Approval",
1547+
modelSelection: {
1548+
provider: "codex",
1549+
model: "gpt-5-codex",
1550+
},
1551+
runtimeMode: "approval-required",
1552+
interactionMode: "default",
1553+
branch: null,
1554+
worktreePath: null,
1555+
createdAt: "2026-02-26T12:30:01.000Z",
1556+
updatedAt: "2026-02-26T12:30:01.000Z",
1557+
},
1558+
});
1559+
1560+
yield* appendAndProject({
1561+
type: "thread.activity-appended",
1562+
eventId: EventId.make("evt-stale-approval-3"),
1563+
aggregateKind: "thread",
1564+
aggregateId: ThreadId.make("thread-stale-approval"),
1565+
occurredAt: "2026-02-26T12:30:02.000Z",
1566+
commandId: CommandId.make("cmd-stale-approval-3"),
1567+
causationEventId: null,
1568+
correlationId: CorrelationId.make("cmd-stale-approval-3"),
1569+
metadata: {},
1570+
payload: {
1571+
threadId: ThreadId.make("thread-stale-approval"),
1572+
activity: {
1573+
id: EventId.make("activity-stale-approval-requested"),
1574+
tone: "approval",
1575+
kind: "approval.requested",
1576+
summary: "Command approval requested",
1577+
payload: {
1578+
requestId: "approval-request-stale-1",
1579+
requestKind: "command",
1580+
},
1581+
turnId: null,
1582+
createdAt: "2026-02-26T12:30:02.000Z",
1583+
},
1584+
},
1585+
});
1586+
1587+
yield* appendAndProject({
1588+
type: "thread.activity-appended",
1589+
eventId: EventId.make("evt-stale-approval-4"),
1590+
aggregateKind: "thread",
1591+
aggregateId: ThreadId.make("thread-stale-approval"),
1592+
occurredAt: "2026-02-26T12:30:03.000Z",
1593+
commandId: CommandId.make("cmd-stale-approval-4"),
1594+
causationEventId: null,
1595+
correlationId: CorrelationId.make("cmd-stale-approval-4"),
1596+
metadata: {},
1597+
payload: {
1598+
threadId: ThreadId.make("thread-stale-approval"),
1599+
activity: {
1600+
id: EventId.make("activity-stale-approval-failed"),
1601+
tone: "error",
1602+
kind: "provider.approval.respond.failed",
1603+
summary: "Provider approval response failed",
1604+
payload: {
1605+
requestId: "approval-request-stale-1",
1606+
detail: "Unknown pending permission request: approval-request-stale-1",
1607+
},
1608+
turnId: null,
1609+
createdAt: "2026-02-26T12:30:03.000Z",
1610+
},
1611+
},
1612+
});
1613+
1614+
const approvalRows = yield* sql<{
1615+
readonly requestId: string;
1616+
readonly status: string;
1617+
readonly resolvedAt: string | null;
1618+
}>`
1619+
SELECT
1620+
request_id AS "requestId",
1621+
status,
1622+
resolved_at AS "resolvedAt"
1623+
FROM projection_pending_approvals
1624+
WHERE request_id = 'approval-request-stale-1'
1625+
`;
1626+
assert.deepEqual(approvalRows, [
1627+
{
1628+
requestId: "approval-request-stale-1",
1629+
status: "resolved",
1630+
resolvedAt: "2026-02-26T12:30:03.000Z",
1631+
},
1632+
]);
1633+
1634+
// MarCode's lazy-hydration design (see FEATURES.md) computes
1635+
// `pendingApprovalCount` on read via SQL aggregate; the
1636+
// `projection_threads.pending_approval_count` column stays dormant
1637+
// (updated only by migration-time backfill), so upstream's runtime
1638+
// assertion on the denormalized column is skipped here.
1639+
}),
1640+
);
1641+
1642+
it.effect("ignores non-stale provider approval response failures", () =>
1643+
Effect.gen(function* () {
1644+
const projectionPipeline = yield* OrchestrationProjectionPipeline;
1645+
const eventStore = yield* OrchestrationEventStore;
1646+
const sql = yield* SqlClient.SqlClient;
1647+
const appendAndProject = (event: Parameters<typeof eventStore.append>[0]) =>
1648+
eventStore
1649+
.append(event)
1650+
.pipe(Effect.flatMap((savedEvent) => projectionPipeline.projectEvent(savedEvent)));
1651+
1652+
yield* appendAndProject({
1653+
type: "project.created",
1654+
eventId: EventId.make("evt-nonstale-approval-1"),
1655+
aggregateKind: "project",
1656+
aggregateId: ProjectId.make("project-nonstale-approval"),
1657+
occurredAt: "2026-02-26T12:45:00.000Z",
1658+
commandId: CommandId.make("cmd-nonstale-approval-1"),
1659+
causationEventId: null,
1660+
correlationId: CorrelationId.make("cmd-nonstale-approval-1"),
1661+
metadata: {},
1662+
payload: {
1663+
projectId: ProjectId.make("project-nonstale-approval"),
1664+
title: "Project Non-Stale Approval",
1665+
workspaceRoot: "/tmp/project-nonstale-approval",
1666+
defaultModelSelection: null,
1667+
scripts: [],
1668+
createdAt: "2026-02-26T12:45:00.000Z",
1669+
updatedAt: "2026-02-26T12:45:00.000Z",
1670+
},
1671+
});
1672+
1673+
yield* appendAndProject({
1674+
type: "thread.created",
1675+
eventId: EventId.make("evt-nonstale-approval-2"),
1676+
aggregateKind: "thread",
1677+
aggregateId: ThreadId.make("thread-nonstale-approval"),
1678+
occurredAt: "2026-02-26T12:45:01.000Z",
1679+
commandId: CommandId.make("cmd-nonstale-approval-2"),
1680+
causationEventId: null,
1681+
correlationId: CorrelationId.make("cmd-nonstale-approval-2"),
1682+
metadata: {},
1683+
payload: {
1684+
threadId: ThreadId.make("thread-nonstale-approval"),
1685+
projectId: ProjectId.make("project-nonstale-approval"),
1686+
title: "Thread Non-Stale Approval",
1687+
modelSelection: {
1688+
provider: "codex",
1689+
model: "gpt-5-codex",
1690+
},
1691+
runtimeMode: "approval-required",
1692+
interactionMode: "default",
1693+
branch: null,
1694+
worktreePath: null,
1695+
createdAt: "2026-02-26T12:45:01.000Z",
1696+
updatedAt: "2026-02-26T12:45:01.000Z",
1697+
},
1698+
});
1699+
1700+
yield* appendAndProject({
1701+
type: "thread.activity-appended",
1702+
eventId: EventId.make("evt-nonstale-approval-3"),
1703+
aggregateKind: "thread",
1704+
aggregateId: ThreadId.make("thread-nonstale-approval"),
1705+
occurredAt: "2026-02-26T12:45:02.000Z",
1706+
commandId: CommandId.make("cmd-nonstale-approval-3"),
1707+
causationEventId: null,
1708+
correlationId: CorrelationId.make("cmd-nonstale-approval-3"),
1709+
metadata: {},
1710+
payload: {
1711+
threadId: ThreadId.make("thread-nonstale-approval"),
1712+
activity: {
1713+
id: EventId.make("activity-nonstale-approval-requested"),
1714+
tone: "approval",
1715+
kind: "approval.requested",
1716+
summary: "Command approval requested",
1717+
payload: {
1718+
requestId: "approval-request-nonstale-existing",
1719+
requestKind: "command",
1720+
},
1721+
turnId: null,
1722+
createdAt: "2026-02-26T12:45:02.000Z",
1723+
},
1724+
},
1725+
});
1726+
1727+
yield* appendAndProject({
1728+
type: "thread.activity-appended",
1729+
eventId: EventId.make("evt-nonstale-approval-4"),
1730+
aggregateKind: "thread",
1731+
aggregateId: ThreadId.make("thread-nonstale-approval"),
1732+
occurredAt: "2026-02-26T12:45:03.000Z",
1733+
commandId: CommandId.make("cmd-nonstale-approval-4"),
1734+
causationEventId: null,
1735+
correlationId: CorrelationId.make("cmd-nonstale-approval-4"),
1736+
metadata: {},
1737+
payload: {
1738+
threadId: ThreadId.make("thread-nonstale-approval"),
1739+
activity: {
1740+
id: EventId.make("activity-nonstale-approval-failed-existing"),
1741+
tone: "error",
1742+
kind: "provider.approval.respond.failed",
1743+
summary: "Provider approval response failed",
1744+
payload: {
1745+
requestId: "approval-request-nonstale-existing",
1746+
detail: "Provider timed out while responding to approval request",
1747+
},
1748+
turnId: TurnId.make("turn-nonstale-failure"),
1749+
createdAt: "2026-02-26T12:45:03.000Z",
1750+
},
1751+
},
1752+
});
1753+
1754+
yield* appendAndProject({
1755+
type: "thread.activity-appended",
1756+
eventId: EventId.make("evt-nonstale-approval-5"),
1757+
aggregateKind: "thread",
1758+
aggregateId: ThreadId.make("thread-nonstale-approval"),
1759+
occurredAt: "2026-02-26T12:45:04.000Z",
1760+
commandId: CommandId.make("cmd-nonstale-approval-5"),
1761+
causationEventId: null,
1762+
correlationId: CorrelationId.make("cmd-nonstale-approval-5"),
1763+
metadata: {},
1764+
payload: {
1765+
threadId: ThreadId.make("thread-nonstale-approval"),
1766+
activity: {
1767+
id: EventId.make("activity-nonstale-approval-failed-missing"),
1768+
tone: "error",
1769+
kind: "provider.approval.respond.failed",
1770+
summary: "Provider approval response failed",
1771+
payload: {
1772+
requestId: "approval-request-nonstale-missing",
1773+
detail: "Provider timed out while responding to approval request",
1774+
},
1775+
turnId: null,
1776+
createdAt: "2026-02-26T12:45:04.000Z",
1777+
},
1778+
},
1779+
});
1780+
1781+
const approvalRows = yield* sql<{
1782+
readonly requestId: string;
1783+
readonly status: string;
1784+
readonly turnId: string | null;
1785+
readonly createdAt: string;
1786+
readonly resolvedAt: string | null;
1787+
}>`
1788+
SELECT
1789+
request_id AS "requestId",
1790+
status,
1791+
turn_id AS "turnId",
1792+
created_at AS "createdAt",
1793+
resolved_at AS "resolvedAt"
1794+
FROM projection_pending_approvals
1795+
WHERE request_id IN (
1796+
'approval-request-nonstale-existing',
1797+
'approval-request-nonstale-missing'
1798+
)
1799+
ORDER BY request_id
1800+
`;
1801+
assert.deepEqual(approvalRows, [
1802+
{
1803+
requestId: "approval-request-nonstale-existing",
1804+
status: "pending",
1805+
turnId: null,
1806+
createdAt: "2026-02-26T12:45:02.000Z",
1807+
resolvedAt: null,
1808+
},
1809+
]);
1810+
1811+
// See note on the "clears stale pending approvals" test above: MarCode
1812+
// reads `pendingApprovalCount` via SQL aggregate rather than
1813+
// maintaining the denormalized `projection_threads.pending_approval_count`
1814+
// column at runtime, so upstream's runtime assertion is skipped.
1815+
}),
1816+
);
1817+
15021818
it.effect("does not fallback-retain messages whose turnId is removed by revert", () =>
15031819
Effect.gen(function* () {
15041820
const projectionPipeline = yield* OrchestrationProjectionPipeline;

0 commit comments

Comments
 (0)