History

fix(server): reject archived queue callers

Julius Marminge committed 2 months ago

Commit e5153f51 parent 818c1ab2 files · +99 −0Browse files

Unified · Split

2 files
  1. apps/server/src/orchestration-v2/Orchestrator.ts +1 −0
  2. apps/server/src/orchestration-v2/runtimeLayer.test.ts +98 −0
apps/server/src/orchestration-v2/Orchestrator.ts+1−0
@@ -596,6 +596,7 @@ const makeOrchestrator = Effect.fn("orchestrationV2.Orchestrator.layer")(functio · 595 unmodified lines
596 );596 );
597 if (597 if (
598 callerProjection.thread.deletedAt !== null ||598 callerProjection.thread.deletedAt !== null ||
599 callerProjection.thread.archivedAt !== null ||
599 callerProjection.thread.projectId !== targetProjection.thread.projectId600 callerProjection.thread.projectId !== targetProjection.thread.projectId
600 ) {601 ) {
601 return yield* new OrchestratorDispatchError({602 return yield* new OrchestratorDispatchError({
apps/server/src/orchestration-v2/runtimeLayer.test.ts+98−0
@@ -1693,6 +1693,104 @@ it.layer(TestLayer)("OrchestrationV2LayerLive lifecycle", (it) => { · 1692 unmodified lines
1693 assert.equal(cancelAgainError._tag, "OrchestratorDispatchError");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
1698it.layer(SharedApplicationDataPlaneTestLayer)("pending provider interruption", (it) => {1796it.layer(SharedApplicationDataPlaneTestLayer)("pending provider interruption", (it) => {