generated-scene: add command-center automation semantics
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
],
|
||||
"source": "scheduled_monitoring_action_trigger_runtime_contract"
|
||||
},
|
||||
"finishedAt": "2026-04-26T05:30:42.489979300+00:00",
|
||||
"finishedAt": "2026-05-06T04:36:29.926950300+00:00",
|
||||
"mode": "dry_run",
|
||||
"previewArtifact": {
|
||||
"actionPlan": [
|
||||
@@ -112,7 +112,7 @@
|
||||
"repetCtrlSend": 0,
|
||||
"sendMessages": 0
|
||||
},
|
||||
"startedAt": "2026-04-26T05:30:42.489979300+00:00",
|
||||
"startedAt": "2026-05-06T04:36:29.926950300+00:00",
|
||||
"status": "dry-run-runtime-pass",
|
||||
"triggerType": "queue",
|
||||
"warnings": [
|
||||
@@ -158,7 +158,7 @@
|
||||
],
|
||||
"source": "scheduled_monitoring_action_trigger_runtime_contract"
|
||||
},
|
||||
"finishedAt": "2026-04-26T05:30:42.260973600+00:00",
|
||||
"finishedAt": "2026-05-06T04:36:29.680658700+00:00",
|
||||
"mode": "monitor_only",
|
||||
"previewArtifact": {
|
||||
"actionPlan": [
|
||||
@@ -226,7 +226,7 @@
|
||||
"repetCtrlSend": 0,
|
||||
"sendMessages": 0
|
||||
},
|
||||
"startedAt": "2026-04-26T05:30:42.260973600+00:00",
|
||||
"startedAt": "2026-05-06T04:36:29.680658700+00:00",
|
||||
"status": "dry-run-runtime-pass",
|
||||
"triggerType": "scheduled",
|
||||
"warnings": [
|
||||
|
||||
@@ -3893,6 +3893,84 @@ fn generator_emits_scheduled_monitoring_action_skill_package() {
|
||||
assert!(test_status.success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_center_materializes_automation_semantics_into_workflow_ir() {
|
||||
let output_root = temp_workspace("sgclaw-command-center-automation-workflow-ir");
|
||||
let skill_root = generate_scheduled_monitoring_action_skill_package(
|
||||
GenerateScheduledMonitoringActionSkillRequest {
|
||||
scene_id: "command-center-fee-control-monitor".to_string(),
|
||||
scene_name: "指挥中心费控异常监测".to_string(),
|
||||
output_root: output_root.clone(),
|
||||
source_evidence_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/monitoring_action_source_evidence_extraction_2026-04-21.json",
|
||||
),
|
||||
ir_contract_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/scheduled_monitoring_action_ir_contract_2026-04-22.json",
|
||||
),
|
||||
trigger_contract_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/scheduled_monitoring_action_trigger_runtime_contract_2026-04-22.json",
|
||||
),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let workflow_ir: serde_json::Value = serde_json::from_str(
|
||||
&fs::read_to_string(skill_root.join("references/workflow-ir.json")).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let action_contracts = workflow_ir["actionContracts"]
|
||||
.as_array()
|
||||
.expect("expected actionContracts array");
|
||||
assert!(
|
||||
action_contracts.iter().any(|item| {
|
||||
item["targetEndpointOrHostCall"] == "repetCtrlSend"
|
||||
|| item["actionId"] == "dispatch_exception_order"
|
||||
}),
|
||||
"expected action contract for repetCtrlSend dispatch"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
workflow_ir["iterationContract"]["sourceCollection"],
|
||||
"pendingList",
|
||||
"expected iteration contract over pendingList"
|
||||
);
|
||||
assert_eq!(
|
||||
workflow_ir["iterationContract"]["iterationMode"],
|
||||
"sequential_per_item",
|
||||
"expected sequential per-item iteration"
|
||||
);
|
||||
|
||||
let queue_transition_rules = workflow_ir["queueTransitionRules"]
|
||||
.as_array()
|
||||
.expect("expected queueTransitionRules array");
|
||||
assert!(
|
||||
queue_transition_rules.iter().any(|item| {
|
||||
item["triggerPoint"] == "on_empty_collection"
|
||||
|| item["transitionId"] == "queue_continue_on_empty"
|
||||
}),
|
||||
"expected queue continue transition for empty collection"
|
||||
);
|
||||
assert!(
|
||||
queue_transition_rules.iter().any(|item| {
|
||||
item["triggerPoint"] == "on_all_items_done"
|
||||
|| item["transitionId"] == "queue_continue_on_done"
|
||||
}),
|
||||
"expected queue continue transition for completed collection"
|
||||
);
|
||||
|
||||
let log_write_contracts = workflow_ir["logWriteContracts"]
|
||||
.as_array()
|
||||
.expect("expected logWriteContracts array");
|
||||
assert!(
|
||||
log_write_contracts.iter().any(|item| {
|
||||
item["targetEndpointOrHostCall"] == "setDisposeLog"
|
||||
|| item["logId"] == "dispose_log_after_dispatch"
|
||||
}),
|
||||
"expected dispose-log contract"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator_preserves_localhost_dependency_as_host_runtime_evidence() {
|
||||
let analysis = analyze_scene_source(Path::new(
|
||||
|
||||
@@ -737,6 +737,233 @@ fn binary_wiring_registry_backed_skill_executes_read_only_scripts_with_runtime_i
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_center_preview_reflects_automation_semantics() {
|
||||
let workspace = temp_workspace("sgclaw-command-center-preview-automation-semantics");
|
||||
let trigger_path = workspace.join("scheduled-trigger.json");
|
||||
let output_path = workspace.join("run-record.json");
|
||||
let config_path = workspace.join("sgclaw_config.json");
|
||||
let rules_path = workspace.join("resources").join("rules.json");
|
||||
let materialization_root = workspace.join("materialized");
|
||||
fs::create_dir_all(&materialization_root).unwrap();
|
||||
write_json(
|
||||
&trigger_path,
|
||||
&scheduled_trigger_with_runtime_inputs("monitor_only"),
|
||||
);
|
||||
write_runtime_rules(&rules_path);
|
||||
|
||||
generate_scheduled_monitoring_action_skill_package(
|
||||
GenerateScheduledMonitoringActionSkillRequest {
|
||||
scene_id: "command-center-fee-control-monitor".to_string(),
|
||||
scene_name: "command-center-fee-control-monitor".to_string(),
|
||||
output_root: materialization_root.clone(),
|
||||
source_evidence_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/monitoring_action_source_evidence_extraction_2026-04-21.json",
|
||||
),
|
||||
ir_contract_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/scheduled_monitoring_action_ir_contract_2026-04-22.json",
|
||||
),
|
||||
trigger_contract_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/scheduled_monitoring_action_trigger_runtime_contract_2026-04-22.json",
|
||||
),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let detect_payload = json!({
|
||||
"type": "scheduled-monitoring-detect-snapshot",
|
||||
"report_name": "command-center-fee-control-monitor",
|
||||
"status": "detect-ok",
|
||||
"workflowId": "command_center_fee_control_monitoring_action",
|
||||
"mode": "monitor_only",
|
||||
"pendingList": [
|
||||
{ "id": "A1", "consNo": "C1", "phone": "13800000000", "abnorType": "fee_control" }
|
||||
],
|
||||
"inputs": {
|
||||
"source": "browser_attached_live_read",
|
||||
"queryAbnorList": [
|
||||
{ "id": "A1", "consNo": "C1", "phone": "13800000000", "abnorType": "fee_control" }
|
||||
],
|
||||
"queryHistoryEnergyCharge": [],
|
||||
"getMonitorLog": { "lastHandled": "2026-04-22T08:00:00Z" },
|
||||
"getOtherIphones": { "holidaySwitch": "off" },
|
||||
"getAllSubMgtOrgTreeByOrgCode": {}
|
||||
},
|
||||
"localStorageSnapshot": {
|
||||
"loginUserInfo": "{\"orgNo\":\"62401\"}",
|
||||
"markToken": "browser-token",
|
||||
"yxClassList": "[{\"orgNo\":\"62401\"}]",
|
||||
"zhzxFkycSendTime": "2026-04-22 08:00:00"
|
||||
},
|
||||
"readDiagnostics": {
|
||||
"source": "browser_attached_live_read",
|
||||
"businessGatewayReadAttempted": true,
|
||||
"localhostReadAttempted": true,
|
||||
"queryAbnorListCount": 1,
|
||||
"queryHistoryEnergyChargeCount": 0
|
||||
},
|
||||
"dependencySnapshot": {
|
||||
"businessReads": [],
|
||||
"localReads": [],
|
||||
"blockedLocalWrites": [],
|
||||
"blockedCalls": ["repetCtrlSend"]
|
||||
},
|
||||
"sideEffectCounters": {
|
||||
"repetCtrlSend": 0,
|
||||
"sendMessages": 0,
|
||||
"callOutLogin": 0,
|
||||
"audioPlay": 0,
|
||||
"exeTQueue": 0,
|
||||
"productionLogWrite": 0
|
||||
}
|
||||
});
|
||||
let (browser_ws_url, browser_server) =
|
||||
start_callback_host_scheduled_monitoring_browser_server(detect_payload);
|
||||
write_browser_config(&config_path, &browser_ws_url);
|
||||
|
||||
let output = run_binary_with_skills_dir_and_config(
|
||||
&trigger_path,
|
||||
&materialization_root.join("skills"),
|
||||
&config_path,
|
||||
&workspace,
|
||||
&output_path,
|
||||
);
|
||||
browser_server.join().unwrap();
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stdout={}\nstderr={}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let record: Value = serde_json::from_str(&fs::read_to_string(output_path).unwrap()).unwrap();
|
||||
let preview_artifact = &record["previewArtifact"];
|
||||
|
||||
assert_eq!(
|
||||
preview_artifact["actionPlan"][0]["actionContractRef"],
|
||||
"dispatch_exception_order"
|
||||
);
|
||||
assert_eq!(preview_artifact["summary"]["queue_transition_count"], 1);
|
||||
assert_eq!(
|
||||
preview_artifact["queueTransitions"][0]["transitionId"],
|
||||
"queue_continue_on_done"
|
||||
);
|
||||
assert_eq!(
|
||||
preview_artifact["logWritePreview"][0]["logId"],
|
||||
"dispose_log_after_dispatch"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_center_empty_pending_list_does_not_emit_log_write_preview() {
|
||||
let workspace = temp_workspace("sgclaw-command-center-empty-preview-semantics");
|
||||
let trigger_path = workspace.join("scheduled-trigger.json");
|
||||
let output_path = workspace.join("run-record.json");
|
||||
let config_path = workspace.join("sgclaw_config.json");
|
||||
let rules_path = workspace.join("resources").join("rules.json");
|
||||
let materialization_root = workspace.join("materialized");
|
||||
fs::create_dir_all(&materialization_root).unwrap();
|
||||
write_json(
|
||||
&trigger_path,
|
||||
&scheduled_trigger_with_runtime_inputs("monitor_only"),
|
||||
);
|
||||
write_runtime_rules(&rules_path);
|
||||
|
||||
generate_scheduled_monitoring_action_skill_package(
|
||||
GenerateScheduledMonitoringActionSkillRequest {
|
||||
scene_id: "command-center-fee-control-monitor".to_string(),
|
||||
scene_name: "command-center-fee-control-monitor".to_string(),
|
||||
output_root: materialization_root.clone(),
|
||||
source_evidence_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/monitoring_action_source_evidence_extraction_2026-04-21.json",
|
||||
),
|
||||
ir_contract_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/scheduled_monitoring_action_ir_contract_2026-04-22.json",
|
||||
),
|
||||
trigger_contract_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/scheduled_monitoring_action_trigger_runtime_contract_2026-04-22.json",
|
||||
),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let detect_payload = json!({
|
||||
"type": "scheduled-monitoring-detect-snapshot",
|
||||
"report_name": "command-center-fee-control-monitor",
|
||||
"status": "detect-ok",
|
||||
"workflowId": "command_center_fee_control_monitoring_action",
|
||||
"mode": "monitor_only",
|
||||
"pendingList": [],
|
||||
"inputs": {
|
||||
"source": "browser_attached_live_read",
|
||||
"queryAbnorList": [],
|
||||
"queryHistoryEnergyCharge": [],
|
||||
"getMonitorLog": {},
|
||||
"getOtherIphones": {},
|
||||
"getAllSubMgtOrgTreeByOrgCode": {}
|
||||
},
|
||||
"localStorageSnapshot": {
|
||||
"loginUserInfo": "{\"orgNo\":\"62401\"}",
|
||||
"markToken": "browser-token",
|
||||
"yxClassList": "[{\"orgNo\":\"62401\"}]",
|
||||
"zhzxFkycSendTime": "2026-04-22 08:00:00"
|
||||
},
|
||||
"readDiagnostics": {
|
||||
"source": "browser_attached_live_read",
|
||||
"businessGatewayReadAttempted": true,
|
||||
"localhostReadAttempted": true,
|
||||
"queryAbnorListCount": 0,
|
||||
"queryHistoryEnergyChargeCount": 0
|
||||
},
|
||||
"dependencySnapshot": {
|
||||
"businessReads": [],
|
||||
"localReads": [],
|
||||
"blockedLocalWrites": [],
|
||||
"blockedCalls": ["repetCtrlSend"]
|
||||
},
|
||||
"sideEffectCounters": {
|
||||
"repetCtrlSend": 0,
|
||||
"sendMessages": 0,
|
||||
"callOutLogin": 0,
|
||||
"audioPlay": 0,
|
||||
"exeTQueue": 0,
|
||||
"productionLogWrite": 0
|
||||
}
|
||||
});
|
||||
let (browser_ws_url, browser_server) =
|
||||
start_callback_host_scheduled_monitoring_browser_server(detect_payload);
|
||||
write_browser_config(&config_path, &browser_ws_url);
|
||||
|
||||
let output = run_binary_with_skills_dir_and_config(
|
||||
&trigger_path,
|
||||
&materialization_root.join("skills"),
|
||||
&config_path,
|
||||
&workspace,
|
||||
&output_path,
|
||||
);
|
||||
browser_server.join().unwrap();
|
||||
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"stdout={}\nstderr={}",
|
||||
String::from_utf8_lossy(&output.stdout),
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
let record: Value = serde_json::from_str(&fs::read_to_string(output_path).unwrap()).unwrap();
|
||||
let preview_artifact = &record["previewArtifact"];
|
||||
|
||||
assert_eq!(preview_artifact["summary"]["pending_count"], 0);
|
||||
assert_eq!(preview_artifact["summary"]["queue_transition_count"], 1);
|
||||
assert_eq!(
|
||||
preview_artifact["queueTransitions"][0]["transitionId"],
|
||||
"queue_continue_on_empty"
|
||||
);
|
||||
assert_eq!(
|
||||
preview_artifact["logWritePreview"].as_array().unwrap().len(),
|
||||
0
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn binary_wiring_browser_attached_passes_platform_service_base_from_config() {
|
||||
let workspace = temp_workspace("sgclaw-scheduled-monitoring-binary-platform-service-base");
|
||||
|
||||
@@ -279,6 +279,58 @@ fn fee_control_materialization_emits_timeout_contract_in_generated_artifacts() {
|
||||
assert!(detect_script.contains("statusOnTimeout"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn command_center_workflow_ir_includes_automation_semantics() {
|
||||
let root = temp_workspace("sgclaw-command-center-automation-semantics");
|
||||
let skill_root = generate_scheduled_monitoring_action_skill_package(
|
||||
GenerateScheduledMonitoringActionSkillRequest {
|
||||
scene_id: "command-center-fee-control-monitor".to_string(),
|
||||
scene_name: "command-center-fee-control-monitor".to_string(),
|
||||
output_root: root.clone(),
|
||||
source_evidence_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/monitoring_action_source_evidence_extraction_2026-04-21.json",
|
||||
),
|
||||
ir_contract_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/scheduled_monitoring_action_ir_contract_2026-04-22.json",
|
||||
),
|
||||
trigger_contract_json: PathBuf::from(
|
||||
"tests/fixtures/generated_scene/scheduled_monitoring_action_trigger_runtime_contract_2026-04-22.json",
|
||||
),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let workflow_ir: Value = serde_json::from_str(
|
||||
&fs::read_to_string(skill_root.join("references/workflow-ir.json")).unwrap(),
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
assert!(
|
||||
workflow_ir.get("actionContracts").is_some(),
|
||||
"workflow-ir.json must include actionContracts"
|
||||
);
|
||||
assert!(
|
||||
workflow_ir.get("iterationContract").is_some(),
|
||||
"workflow-ir.json must include iterationContract"
|
||||
);
|
||||
assert!(
|
||||
workflow_ir.get("executionFlow").is_some(),
|
||||
"workflow-ir.json must include executionFlow"
|
||||
);
|
||||
assert!(
|
||||
workflow_ir.get("resultStateMachines").is_some(),
|
||||
"workflow-ir.json must include resultStateMachines"
|
||||
);
|
||||
assert!(
|
||||
workflow_ir.get("queueTransitionRules").is_some(),
|
||||
"workflow-ir.json must include queueTransitionRules"
|
||||
);
|
||||
assert!(
|
||||
workflow_ir.get("logWriteContracts").is_some(),
|
||||
"workflow-ir.json must include logWriteContracts"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn available_balance_and_archive_materialization_emit_explicit_dependency_classification_in_references() {
|
||||
let example_output = Command::new("cargo")
|
||||
|
||||
Reference in New Issue
Block a user