fix(awooop): keep closed work progress monotonic
All checks were successful
CD Pipeline / workflow-shape (push) Successful in 0s
CD Pipeline / cancel-stale-cd (push) Has been skipped
CD Pipeline / tests (push) Successful in 6m10s
CD Pipeline / build-and-deploy (push) Successful in 8m50s
CD Pipeline / post-deploy-checks (push) Successful in 3m1s

This commit is contained in:
ogt
2026-07-11 17:28:20 +08:00
parent 15a05042e7
commit 118715b004
3 changed files with 119 additions and 3 deletions

View File

@@ -33,6 +33,7 @@ import {
import { Link } from "@/i18n/routing";
import { AutonomousRuntimeReceiptPanel } from "@/components/awooop/autonomous-runtime-receipt-panel";
import { IncidentEvidenceHeader } from "@/components/awooop/incident-evidence-header";
import { isMonotonicPriorityWorkOrderProjection } from "@/lib/priority-work-order-projection";
import { getRuntimeApiBaseUrl } from "@/lib/runtime-api-base";
import {
AwoooPStatusChainPanel,
@@ -12809,7 +12810,7 @@ export default function AwoooPWorkItemsPage() {
for (const url of urls) {
const nextPriorityWorkOrder =
await fetchJson<PriorityWorkOrderResponse>(url, 12000);
if (nextPriorityWorkOrder?.commander_inserted_requirement_work_items?.length) {
if (isMonotonicPriorityWorkOrderProjection(nextPriorityWorkOrder)) {
setPriorityWorkOrderReadback(nextPriorityWorkOrder);
setPriorityWorkOrderLoading(false);
return nextPriorityWorkOrder;
@@ -12817,7 +12818,6 @@ export default function AwoooPWorkItemsPage() {
}
await new Promise((resolve) => window.setTimeout(resolve, 1200));
}
setPriorityWorkOrderLoading(false);
return null;
}, []);
@@ -12964,7 +12964,23 @@ export default function AwoooPWorkItemsPage() {
}, [effectiveFocusedIncidentId, projectId]);
useEffect(() => {
fetchPriorityWorkOrderReadback();
let cancelled = false;
let retryTimer: number | null = null;
const loadPriorityWorkOrder = async () => {
const readback = await fetchPriorityWorkOrderReadback();
if (!cancelled && !readback) {
retryTimer = window.setTimeout(loadPriorityWorkOrder, 4000);
}
};
void loadPriorityWorkOrder();
return () => {
cancelled = true;
if (retryTimer !== null) {
window.clearTimeout(retryTimer);
}
};
}, [fetchPriorityWorkOrderReadback]);
useEffect(() => {

View File

@@ -0,0 +1,53 @@
import { describe, expect, it } from "vitest";
import { isMonotonicPriorityWorkOrderProjection } from "../priority-work-order-projection";
const closedWorkItems = [
"AIA-P0-001",
"AIA-P0-011",
"AIA-P0-002",
"AIA-P0-003",
"AIA-P0-004",
"AIA-P0-005",
].map((id) => ({ id, status: "done" }));
describe("isMonotonicPriorityWorkOrderProjection", () => {
it("accepts the production-closed 30 percent projection", () => {
expect(
isMonotonicPriorityWorkOrderProjection({
commander_inserted_requirement_work_items: [{}],
ai_automation_program_ledger: {
work_items: [...closedWorkItems, { id: "AIA-P0-006", status: "in_progress" }],
summary: { done_count: 6, completion_percent: 30 },
},
})
).toBe(true);
});
it("rejects a transient zero projection that reopens closed work", () => {
expect(
isMonotonicPriorityWorkOrderProjection({
commander_inserted_requirement_work_items: [{}],
ai_automation_program_ledger: {
work_items: closedWorkItems.map((item) => ({
...item,
status: "in_progress",
})),
summary: { done_count: 0, completion_percent: 0 },
},
})
).toBe(false);
});
it("accepts future progress above the current closure floor", () => {
expect(
isMonotonicPriorityWorkOrderProjection({
commander_inserted_requirement_work_items: [{}],
ai_automation_program_ledger: {
work_items: [...closedWorkItems, { id: "AIA-P0-006", status: "done" }],
summary: { done_count: 7, completion_percent: 35 },
},
})
).toBe(true);
});
});

View File

@@ -0,0 +1,47 @@
const MONOTONIC_CLOSED_PROGRAM_WORK_ITEM_IDS = [
"AIA-P0-001",
"AIA-P0-011",
"AIA-P0-002",
"AIA-P0-003",
"AIA-P0-004",
"AIA-P0-005",
] as const;
type PriorityWorkOrderProjection = {
commander_inserted_requirement_work_items?: unknown[] | null;
ai_automation_program_ledger?: {
work_items?: Array<{
id?: string | null;
status?: string | null;
}> | null;
summary?: {
done_count?: number | null;
completion_percent?: number | null;
} | null;
} | null;
};
export function isMonotonicPriorityWorkOrderProjection(
projection: PriorityWorkOrderProjection | null | undefined
): boolean {
if (!projection?.commander_inserted_requirement_work_items?.length) {
return false;
}
const program = projection.ai_automation_program_ledger;
const workItems = program?.work_items ?? [];
const statusById = new Map(
workItems.map((item) => [item.id ?? "", item.status ?? ""])
);
const closureFloorSatisfied = MONOTONIC_CLOSED_PROGRAM_WORK_ITEM_IDS.every(
(workItemId) => statusById.get(workItemId) === "done"
);
const doneCount = Number(program?.summary?.done_count ?? 0);
const completionPercent = Number(program?.summary?.completion_percent ?? 0);
return (
closureFloorSatisfied &&
doneCount >= MONOTONIC_CLOSED_PROGRAM_WORK_ITEM_IDS.length &&
completionPercent >= 30
);
}