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
596596 );
597597 if (
598598 callerProjection.thread.deletedAt !== null ||
599+ callerProjection.thread.archivedAt !== null ||
599600 callerProjection.thread.projectId !== targetProjection.thread.projectId
600601 ) {
601602 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
16931693 assert.equal(cancelAgainError._tag, "OrchestratorDispatchError");
16941694 }),
16951695 );
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+ );
16961794 });
16971795
16981796 it.layer(SharedApplicationDataPlaneTestLayer)("pending provider interruption", (it) => {