fix: restore investigation and appointment task loading

Server responses now include source.replay_context, including null for ordinary tasks. The strict frontend source schema rejected the new field and blocked the shared task snapshot and detail loading path.

Validate replay metadata explicitly and cover investigation, appointment, and scheduled task contracts plus browser loading. Six acceptance regressions failed before the fix; all 877 unit tests and the focused browser loading test passed after it. Lint and type checking passed. The separate approval browser case remains blocked by its reconnect-toast assertion.
This commit is contained in:
2026-11-02 18:37:55 +08:00
parent 74bf7c64c4
commit 0ec74b15b2
3 changed files with 112 additions and 1 deletions
@@ -42,6 +42,35 @@ function task(generation: number | null | undefined = 2) {
afterEach(() => vi.unstubAllGlobals());
describe("operational task contract", () => {
describe.each(["event_investigation", "external_work_order", "scheduled_flow"])("%s replay context", (taskType) => {
const replayContext = { replay_id: id, knowledge_cutoff: time, batch_sequence: 1 };
it.each([null, replayContext])("loads summaries and details with replay_context %j", (context) => {
const value = task();
const detail = { ...value, task_type: taskType, source: { ...value.source, replay_context: context } };
const {
latest_plan: _latestPlan, latest_report: _latestReport, evidence: _evidence,
actions: _actions, permissions: _permissions, ...summary
} = detail;
expect(parseOperationalTaskSummary(summary).source).toHaveProperty("replay_context", context);
expect(parseOperationalTask(detail).source).toHaveProperty("replay_context", context);
});
it.each([
{ ...replayContext, replay_id: "not-a-uuid" },
{ ...replayContext, knowledge_cutoff: "not-a-date" },
{ ...replayContext, batch_sequence: 0 },
{ ...replayContext, batch_sequence: 1.5 },
{ ...replayContext, unexpected: true }
])("rejects invalid replay metadata %j", (context) => {
const value = task();
expect(() => parseOperationalTask({
...value, task_type: taskType, source: { ...value.source, replay_context: context }
})).toThrow();
});
});
it("loads task runs used to resolve a selected report round", async () => {
const run = {
run_id: id,
@@ -13,7 +13,12 @@ const taskSourceSchema = z.object({
external_id: z.string().min(1).nullable(),
external_revision: z.number().int().positive().nullable(),
event_kind: z.string().min(1).nullable(),
deleted_at: workbenchDateTimeSchema.nullish()
deleted_at: workbenchDateTimeSchema.nullish(),
replay_context: z.object({
replay_id: z.string().uuid(),
knowledge_cutoff: workbenchDateTimeSchema,
batch_sequence: z.number().int().positive()
}).strict().nullish()
}).strict();
const taskTriggerSchema = z.object({
kind: z.enum(["schedule", "external_sync", "event", "manual"]),