fix: require idle workers for submit

This commit is contained in:
2026-09-23 00:36:54 +09:00
parent d8444e49bd
commit 9857bf04f7
16 changed files with 451 additions and 264 deletions
@@ -20,15 +20,7 @@ const base = {
hasAttachments: false,
};
Deno.test("running Composer enables Queue Submit and Notify but not immediate Submit", () => {
assertEquals(
canDeliverComposerDraft({
...base,
delivery: "queue",
workerState: "running",
}),
true,
);
Deno.test("running Composer enables Notify but not Submit", () => {
assertEquals(
canDeliverComposerDraft({
...base,
@@ -47,16 +39,8 @@ Deno.test("running Composer enables Queue Submit and Notify but not immediate Su
);
});
Deno.test("running Queue Submit and Notify dispatch their protocol methods", () => {
Deno.test("running Notify dispatches its protocol method", () => {
const sent: string[] = [];
assertEquals(
sendComposerDelivery(
{ ...base, delivery: "queue", workerState: "running" },
"submit",
(method) => sent.push(method),
),
true,
);
assertEquals(
sendComposerDelivery(
{ ...base, delivery: "notify", workerState: "running" },
@@ -65,10 +49,10 @@ Deno.test("running Queue Submit and Notify dispatch their protocol methods", ()
),
true,
);
assertEquals(sent.join(","), "submit,notify");
assertEquals(sent.join(","), "notify");
});
Deno.test("idle Composer enables only immediate Submit", () => {
Deno.test("idle Composer enables only Submit", () => {
assertEquals(
canDeliverComposerDraft({
...base,
@@ -77,14 +61,6 @@ Deno.test("idle Composer enables only immediate Submit", () => {
}),
true,
);
assertEquals(
canDeliverComposerDraft({
...base,
delivery: "queue",
workerState: "idle",
}),
false,
);
assertEquals(
canDeliverComposerDraft({
...base,
@@ -95,11 +71,30 @@ Deno.test("idle Composer enables only immediate Submit", () => {
);
});
Deno.test("running delivery remains fenced by protocol, send state, and payload kind", () => {
Deno.test("paused Composer enables neither Submit nor Notify", () => {
assertEquals(
canDeliverComposerDraft({
...base,
delivery: "queue",
delivery: "submit",
workerState: "paused",
}),
false,
);
assertEquals(
canDeliverComposerDraft({
...base,
delivery: "notify",
workerState: "paused",
}),
false,
);
});
Deno.test("running Notify remains fenced by protocol, send state, and payload kind", () => {
assertEquals(
canDeliverComposerDraft({
...base,
delivery: "notify",
workerState: "running",
protocolOpen: false,
}),
@@ -123,14 +118,4 @@ Deno.test("running delivery remains fenced by protocol, send state, and payload
}),
false,
);
assertEquals(
canDeliverComposerDraft({
...base,
delivery: "queue",
workerState: "running",
hasText: false,
hasAttachments: true,
}),
true,
);
});
@@ -1,4 +1,4 @@
export type ComposerDelivery = "submit" | "queue" | "notify";
export type ComposerDelivery = "submit" | "notify";
export type ComposerDeliveryState = {
delivery: ComposerDelivery;
@@ -11,7 +11,7 @@ export type ComposerDeliveryState = {
/**
* Resolve whether the current Composer draft can use one delivery action.
* Immediate Submit is idle-only; Queue and Notify are running-only.
* Immediate Submit is idle-only; Notify is running-only.
*/
export function canDeliverComposerDraft(state: ComposerDeliveryState): boolean {
if (!state.protocolOpen || state.sending) return false;
@@ -20,8 +20,6 @@ export function canDeliverComposerDraft(state: ComposerDeliveryState): boolean {
switch (state.delivery) {
case "submit":
return state.workerState === "idle" && hasInput;
case "queue":
return state.workerState === "running" && hasInput;
case "notify":
return state.workerState === "running" && state.hasText &&
!state.hasAttachments;
@@ -1239,7 +1239,7 @@ Deno.test("Web Console switches main and direct SubWorker views from the Tasks r
);
});
Deno.test("Web Console uses Notify while running and exposes durable pending controls", async () => {
Deno.test("Web Console uses Notify while running and keeps recovery pending controls", async () => {
const consolePage = await Deno.readTextFile(
new URL(
"./../../../routes/w/[workspaceId]/workers/[workerRef]/console/+page.svelte",
@@ -1257,12 +1257,8 @@ Deno.test("Web Console uses Notify while running and exposes durable pending con
'method: "cancel_pending_submission"',
'method: "clear_pending_submissions"',
'method: "continue_pending"',
"handleQueueSubmit",
"handleNotifySubmit",
'submitDraft(composerInputElement?.snapshot() ?? draft, "queue")',
"disabled={!canQueueDraft}",
"disabled={!canNotifyDraft}",
">Queue Submit</button>",
">Notify</button>",
]
) {
@@ -1272,6 +1268,13 @@ Deno.test("Web Console uses Notify while running and exposes durable pending con
);
}
assert(
!consolePage.includes("handleQueueSubmit") &&
!consolePage.includes(">Queue Submit</button>") &&
!consolePage.includes('delivery: "queue"'),
"running Console must not offer Submit queueing; callers should use Notify",
);
const userCase = consolePage.slice(
consolePage.indexOf('case "user":'),
consolePage.indexOf('case "compact":'),
@@ -269,16 +269,6 @@
hasAttachments: draftHasAttachments,
}),
);
const canQueueDraft = $derived(
canDeliverComposerDraft({
delivery: "queue",
workerState,
protocolOpen: protocolState === "open",
sending,
hasText: draftHasText,
hasAttachments: draftHasAttachments,
}),
);
const canNotifyDraft = $derived(
canDeliverComposerDraft({
delivery: "notify",
@@ -729,10 +719,6 @@
void submitDraft(composerInputElement?.snapshot() ?? draft);
}
function handleQueueSubmit() {
void submitDraft(composerInputElement?.snapshot() ?? draft, "queue");
}
function handleNotifySubmit() {
void submitDraft(composerInputElement?.snapshot() ?? draft, "notify");
}
@@ -842,7 +828,7 @@
) {
if (delivery === "notify" && attachments.length > 0) {
composerNotice = null;
sendError = "Notify accepts text only; remove attachments or queue a Submit.";
sendError = "Notify accepts text only; remove attachments or wait until the Worker is idle to Submit.";
return;
}
const incompleteAttachment = attachments.find((attachment) =>
@@ -2151,11 +2137,6 @@
</div>
<div class="composer-actions">
{#if workerRunning}
<button
type="button"
disabled={!canQueueDraft}
onclick={handleQueueSubmit}
>Queue Submit</button>
<button
type="button"
disabled={!canNotifyDraft}