feat(tasks): define investigation report contract and history
This commit is contained in:
@@ -104,7 +104,7 @@ async def test_postgres_task_artifacts_approval_and_project_isolation_roll_back(
|
||||
{"id": "sample", "title": "现场采样", "owner": "human", "status": "waiting",
|
||||
"detail": "等待外部反馈", "evidence_refs": []}],
|
||||
"edges": [{"from": "review", "to": "sample"}]},
|
||||
"report": {"title": "调查阶段结论", "current_conclusion": "待现场核验", "conclusion_boundary": "合成输入",
|
||||
"report": {"stage": "final", "outcome": "concluded", "stop_reason": None, "data_gaps": [], "title": "调查阶段结论", "current_conclusion": "待现场核验", "conclusion_boundary": "合成输入",
|
||||
"evidence_refs": ["synthetic-1"], "pending_items": ["现场采样"]},
|
||||
})
|
||||
published = await service.publish_artifacts(task.task_id, artifact)
|
||||
@@ -120,7 +120,7 @@ async def test_postgres_task_artifacts_approval_and_project_isolation_roll_back(
|
||||
source_session_id="test-investigation-session",
|
||||
),
|
||||
)
|
||||
assert task.status == "waiting_human"
|
||||
assert task.status == "completed"
|
||||
action = await service.propose_action(task.task_id, ActionProposalCommand(
|
||||
request_id=uuid4(), kind="create_work_order", reason="需要现场采样", evidence_refs=["synthetic-1"],
|
||||
payload={"title": "采样", "description": "合成核验", "work_order_type": "sampling"},
|
||||
@@ -135,13 +135,18 @@ async def test_postgres_task_artifacts_approval_and_project_isolation_roll_back(
|
||||
item for item in member_notifications
|
||||
if item.kind == "action_approval_pending"
|
||||
)
|
||||
human_input = next(
|
||||
report_notice = next(
|
||||
item for item in member_notifications
|
||||
if item.kind == "human_input_required"
|
||||
if item.kind == "report_updated"
|
||||
)
|
||||
assert not any(item.kind in {
|
||||
"task_created", "report_updated", "task_completed"
|
||||
"task_created", "human_input_required", "task_completed"
|
||||
} for item in member_notifications)
|
||||
assert report_notice.report_reference.revision == 1
|
||||
assert report_notice.report_reference.execution_generation == 1
|
||||
history, next_offset = await service.list_reports(task.task_id, 20, 0)
|
||||
assert next_offset is None and history[0].revision == 1
|
||||
assert (await service.report(task.task_id, 1)).report.model_dump() == artifact.report.model_dump()
|
||||
assert action_pending.required_permission == action.required_approval_permission
|
||||
|
||||
viewer_context = ProjectContext(
|
||||
@@ -158,7 +163,7 @@ async def test_postgres_task_artifacts_approval_and_project_isolation_roll_back(
|
||||
viewer_notifications, _ = await viewer_service.list_notifications(
|
||||
unread_only=True, limit=100, offset=0
|
||||
)
|
||||
assert human_input.notification_id in {
|
||||
assert report_notice.notification_id in {
|
||||
item.notification_id for item in viewer_notifications
|
||||
}
|
||||
assert action_pending.notification_id not in {
|
||||
@@ -166,9 +171,9 @@ async def test_postgres_task_artifacts_approval_and_project_isolation_roll_back(
|
||||
}
|
||||
|
||||
acknowledged = await service.acknowledge_notification(
|
||||
human_input.notification_id,
|
||||
report_notice.notification_id,
|
||||
NotificationAcknowledgeCommand(
|
||||
request_id=uuid4(), expected_version=human_input.version
|
||||
request_id=uuid4(), expected_version=report_notice.version
|
||||
),
|
||||
)
|
||||
assert acknowledged.status == "read"
|
||||
@@ -184,21 +189,21 @@ async def test_postgres_task_artifacts_approval_and_project_isolation_roll_back(
|
||||
)
|
||||
persisted_created = next(
|
||||
item for item in persisted_notifications
|
||||
if item.notification_id == human_input.notification_id
|
||||
if item.notification_id == report_notice.notification_id
|
||||
)
|
||||
assert persisted_created.status == "read"
|
||||
assert persisted_created.version == human_input.version + 1
|
||||
assert persisted_created.version == report_notice.version + 1
|
||||
assert persisted_created.read_by == f"principal:{context.user_id}"
|
||||
unread_notifications, _ = await persisted_service.list_notifications(
|
||||
unread_only=True, limit=100, offset=0
|
||||
)
|
||||
assert human_input.notification_id not in {
|
||||
assert report_notice.notification_id not in {
|
||||
item.notification_id for item in unread_notifications
|
||||
}
|
||||
viewer_after_member_ack, _ = await viewer_service.list_notifications(
|
||||
unread_only=True, limit=100, offset=0
|
||||
)
|
||||
assert human_input.notification_id in {
|
||||
assert report_notice.notification_id in {
|
||||
item.notification_id for item in viewer_after_member_ack
|
||||
}
|
||||
action = await service.decide_action(task.task_id, action.action_id, ActionDecisionCommand(
|
||||
@@ -259,6 +264,10 @@ async def test_postgres_task_artifacts_approval_and_project_isolation_roll_back(
|
||||
context, permissions_for_context(context))
|
||||
with pytest.raises(DomainError):
|
||||
await outsider.detail(task.task_id)
|
||||
await fresh_service.publish_artifacts(task.task_id, artifact.model_copy(update={
|
||||
"request_id": uuid4(), "run_id": resumed.active_run.run_id,
|
||||
"source_session_id": "test-investigation-followup-session",
|
||||
}))
|
||||
sampling = await fresh_service.propose_action(task.task_id, ActionProposalCommand(
|
||||
request_id=uuid4(), kind="create_work_order", reason="需要补充采样",
|
||||
evidence_refs=["synthetic-1"],
|
||||
@@ -307,5 +316,35 @@ async def test_postgres_task_artifacts_approval_and_project_isolation_roll_back(
|
||||
dispatcher, "Bearer TEST_ONLY")
|
||||
assert calls == ["POST", "GET", "GET"]
|
||||
assert (await fresh_service.detail(task.task_id)).status == "queued"
|
||||
# A separate investigation ends normally on an evidenced empty query.
|
||||
missing = await service.create(command.model_copy(update={
|
||||
"request_id": uuid4(), "title": "AUTOTEST 缺数退出",
|
||||
}))
|
||||
missing_run = await service.claim_run(missing.task_id, missing.active_run.run_id, AgentRunCommand(
|
||||
request_id=missing.active_run.execution_request_id,
|
||||
expected_revision=missing.active_run.revision, source_session_id="missing-data-session"))
|
||||
exit_artifact = AgentArtifactsCommand(
|
||||
request_id=uuid4(), source_session_id="missing-data-session", run_id=missing_run.run_id,
|
||||
evidence=[{"evidence_id": "empty-sample", "kind": "synthetic", "summary": "查询成功,无采样记录",
|
||||
"source_label": "合成空查询", "observed_at": now.isoformat()}],
|
||||
plan={"schema": "execution-plan@1", "title": "调查", "reason": "关键数据不足",
|
||||
"nodes": [{"id": "query", "title": "核对采样", "owner": "agent", "status": "completed",
|
||||
"detail": "查询范围内无记录", "evidence_refs": ["empty-sample"]}]},
|
||||
report={"title": "无法形成结论", "current_conclusion": "无法确定投诉原因",
|
||||
"conclusion_boundary": "缺少末梢采样", "evidence_refs": ["empty-sample"],
|
||||
"stage": "final", "outcome": "insufficient_data", "stop_reason": "关键采样数据为空",
|
||||
"data_gaps": [{"gap_id": "sample", "node_id": "query", "missing_data": "末梢采样",
|
||||
"scope": "投诉时段", "impact": "无法判断异常来源", "required_data": "末梢采样结果",
|
||||
"evidence_refs": ["empty-sample"], "blocking": True}]})
|
||||
exited = await service.publish_artifacts(missing.task_id, exit_artifact)
|
||||
await service.complete_run(missing.task_id, missing_run.run_id, AgentRunCommand(
|
||||
request_id=uuid4(), expected_revision=exited.task.active_run.revision,
|
||||
source_session_id="missing-data-session"))
|
||||
restored = await fresh_service.detail(missing.task_id)
|
||||
assert restored.lifecycle_status == "completed"
|
||||
assert restored.active_run is None
|
||||
assert restored.latest_report.report.outcome == "insufficient_data"
|
||||
assert restored.latest_report.report.data_gaps[0].blocking is True
|
||||
assert not restored.actions
|
||||
finally:
|
||||
await conn.close()
|
||||
|
||||
Reference in New Issue
Block a user