| @@ -1693,6 +1693,104 @@ it.layer(TestLayer)("OrchestrationV2LayerLive lifecycle", (it) => { · 1692 unmodified lines |
| 1693 | 1693 | assert.equal(cancelAgainError._tag, "OrchestratorDispatchError"); |
| 1694 | 1694 | }), |
| 1695 | 1695 | ); |
| 1696 | + |
| 1697 | + it.effect("rejects queued prompt mutations from an archived caller", () => |
| 1698 | + Effect.gen(function* () { |
| 1699 | + const orchestrator = yield* OrchestratorV2; |
| 1700 | + const projectId = ProjectId.make("runtime-layer-queued-archived-caller-project"); |
| 1701 | + const callerThreadId = ThreadId.make("runtime-layer-queued-archived-caller"); |
| 1702 | + const targetThreadId = ThreadId.make("runtime-layer-queued-archived-target"); |
| 1703 | + |
| 1704 | + for (const [threadId, title] of [ |
| 1705 | + [callerThreadId, "Archived queue caller"], |
| 1706 | + [targetThreadId, "Archived queue target"], |
| 1707 | + ] as const) { |
| 1708 | + yield* orchestrator.dispatch({ |
| 1709 | + type: "thread.create", |
| 1710 | + createdBy: "user", |
| 1711 | + creationSource: "web", |
| 1712 | + commandId: CommandId.make(`command:${threadId}:create`), |
| 1713 | + threadId, |
| 1714 | + projectId, |
| 1715 | + title, |
| 1716 | + modelSelection, |
| 1717 | + runtimeMode: "full-access", |
| 1718 | + interactionMode: "default", |
| 1719 | + branch: null, |
| 1720 | + worktreePath: `/tmp/${threadId}`, |
| 1721 | + }); |
| 1722 | + } |
| 1723 | + yield* orchestrator.dispatch({ |
| 1724 | + type: "message.dispatch", |
| 1725 | + createdBy: "user", |
| 1726 | + creationSource: "web", |
| 1727 | + commandId: CommandId.make("runtime-layer-queued-archived-target-active"), |
| 1728 | + threadId: targetThreadId, |
| 1729 | + messageId: MessageId.make("runtime-layer-queued-archived-target-active"), |
| 1730 | + text: "Keep the target active.", |
| 1731 | + attachments: [], |
| 1732 | + modelSelection, |
| 1733 | + dispatchMode: { type: "start_immediately" }, |
| 1734 | + }); |
| 1735 | + const queuedMessageId = MessageId.make("runtime-layer-queued-archived-target-queued"); |
| 1736 | + yield* orchestrator.dispatch({ |
| 1737 | + type: "message.dispatch", |
| 1738 | + createdBy: "user", |
| 1739 | + creationSource: "web", |
| 1740 | + commandId: CommandId.make("runtime-layer-queued-archived-target-queued"), |
| 1741 | + threadId: targetThreadId, |
| 1742 | + messageId: queuedMessageId, |
| 1743 | + text: "Do not mutate this queued prompt.", |
| 1744 | + attachments: [], |
| 1745 | + modelSelection, |
| 1746 | + dispatchMode: { type: "queue_after_active" }, |
| 1747 | + }); |
| 1748 | + const queuedRun = (yield* orchestrator.getThreadProjection(targetThreadId)).runs.find( |
| 1749 | + (run) => run.userMessageId === queuedMessageId, |
| 1750 | + ); |
| 1751 | + assert.isDefined(queuedRun); |
| 1752 | + yield* orchestrator.dispatch({ |
| 1753 | + type: "thread.archive", |
| 1754 | + commandId: CommandId.make("runtime-layer-queued-archived-caller-archive"), |
| 1755 | + threadId: callerThreadId, |
| 1756 | + }); |
| 1757 | + |
| 1758 | + const policyCeiling = { |
| 1759 | + callerThreadId, |
| 1760 | + runtimeMode: "full-access" as const, |
| 1761 | + interactionMode: "default" as const, |
| 1762 | + }; |
| 1763 | + const editError = yield* orchestrator |
| 1764 | + .dispatch({ |
| 1765 | + type: "queued-run.edit", |
| 1766 | + commandId: CommandId.make("runtime-layer-queued-archived-caller-edit"), |
| 1767 | + threadId: targetThreadId, |
| 1768 | + runId: queuedRun.id, |
| 1769 | + text: "This edit must be rejected.", |
| 1770 | + policyCeiling, |
| 1771 | + }) |
| 1772 | + .pipe(Effect.flip); |
| 1773 | + assert.equal(editError._tag, "OrchestratorDispatchError"); |
| 1774 | + const promoteError = yield* orchestrator |
| 1775 | + .dispatch({ |
| 1776 | + type: "queued-message.promote-to-steer", |
| 1777 | + commandId: CommandId.make("runtime-layer-queued-archived-caller-promote"), |
| 1778 | + threadId: targetThreadId, |
| 1779 | + queuedRunId: queuedRun.id, |
| 1780 | + targetRunId: RunId.make("runtime-layer-queued-archived-target-active-run"), |
| 1781 | + policyCeiling, |
| 1782 | + }) |
| 1783 | + .pipe(Effect.flip); |
| 1784 | + assert.equal(promoteError._tag, "OrchestratorDispatchError"); |
| 1785 | + |
| 1786 | + const projection = yield* orchestrator.getThreadProjection(targetThreadId); |
| 1787 | + assert.equal( |
| 1788 | + projection.messages.find((message) => message.id === queuedMessageId)?.text, |
| 1789 | + "Do not mutate this queued prompt.", |
| 1790 | + ); |
| 1791 | + assert.equal(projection.runs.find((run) => run.id === queuedRun.id)?.status, "queued"); |
| 1792 | + }), |
| 1793 | + ); |
| 1696 | 1794 | }); |
| 1697 | 1795 | |
| 1698 | 1796 | it.layer(SharedApplicationDataPlaneTestLayer)("pending provider interruption", (it) => { |