generated-scene: add scheduled monitoring runtime and helper lifecycle hardening
This commit is contained in:
@@ -7,8 +7,10 @@ use sgclaw::generated_scene::analyzer::{
|
||||
G2FamilyVariant, SceneKind, ToolKind,
|
||||
};
|
||||
use sgclaw::generated_scene::generator::{
|
||||
compute_readiness_for_test, generate_scene_package, resolve_scene_ir_for_test,
|
||||
GenerateSceneRequest,
|
||||
compute_readiness_for_test, generate_monitoring_action_detect_preview_package,
|
||||
generate_scene_package, generate_scheduled_monitoring_action_skill_package,
|
||||
resolve_scene_ir_for_test, GenerateMonitoringActionPreviewRequest,
|
||||
GenerateScheduledMonitoringActionSkillRequest, GenerateSceneRequest,
|
||||
};
|
||||
use sgclaw::generated_scene::ir::{
|
||||
ApiEndpointIr, ArtifactContractIr, BootstrapIr, ModeConditionIr, ModeIr, ReadinessIr,
|
||||
@@ -1381,9 +1383,7 @@ fn generator_escapes_request_mapping_fields_for_valid_toml() {
|
||||
let output_root = temp_workspace("sgclaw-scene-generator-sweep-078-valid-toml");
|
||||
|
||||
generate_scene_package(GenerateSceneRequest {
|
||||
source_dir: PathBuf::from(
|
||||
"D:/desk/智能体资料/全量业务场景/一平台场景/线损同期差异报表",
|
||||
),
|
||||
source_dir: PathBuf::from("D:/desk/智能体资料/全量业务场景/一平台场景/线损同期差异报表"),
|
||||
scene_id: "sweep-078-scene".to_string(),
|
||||
scene_name: "线损同期差异报表".to_string(),
|
||||
scene_kind: Some(SceneKind::ReportCollection),
|
||||
@@ -1505,7 +1505,9 @@ fn generator_recovers_local_doc_residual_packages_from_source_evidence() {
|
||||
scene_info_json: None,
|
||||
scene_ir_json: None,
|
||||
})
|
||||
.unwrap_or_else(|err| panic!("{scene_id} should generate after local-doc residual closure: {err}"));
|
||||
.unwrap_or_else(|err| {
|
||||
panic!("{scene_id} should generate after local-doc residual closure: {err}")
|
||||
});
|
||||
|
||||
let generated_report: SceneIr = serde_json::from_str(
|
||||
&fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap(),
|
||||
@@ -1525,8 +1527,14 @@ fn generator_recovers_local_doc_residual_packages_from_source_evidence() {
|
||||
"scene_id={scene_id}; steps={:?}",
|
||||
generated_report.workflow_steps
|
||||
);
|
||||
assert!(skill_root.join("SKILL.toml").exists(), "scene_id={scene_id}");
|
||||
assert!(skill_root.join("scene.toml").exists(), "scene_id={scene_id}");
|
||||
assert!(
|
||||
skill_root.join("SKILL.toml").exists(),
|
||||
"scene_id={scene_id}"
|
||||
);
|
||||
assert!(
|
||||
skill_root.join("scene.toml").exists(),
|
||||
"scene_id={scene_id}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3048,6 +3056,7 @@ fn generator_blocks_incomplete_g6_host_bridge_contract() {
|
||||
column_defs: Vec::new(),
|
||||
confidence: 0.7,
|
||||
uncertainties: Vec::new(),
|
||||
monitoring_action_workflow: None,
|
||||
};
|
||||
scene_ir.workflow_steps.push(WorkflowStepIr {
|
||||
step_type: "host_bridge".to_string(),
|
||||
@@ -3182,6 +3191,7 @@ fn generator_blocks_incomplete_g7_inventory_contract() {
|
||||
column_defs: Vec::new(),
|
||||
confidence: 0.7,
|
||||
uncertainties: Vec::new(),
|
||||
monitoring_action_workflow: None,
|
||||
};
|
||||
|
||||
let error = generate_scene_package(GenerateSceneRequest {
|
||||
@@ -3408,6 +3418,7 @@ fn generator_blocks_incomplete_g8_local_doc_pipeline_contract() {
|
||||
column_defs: Vec::new(),
|
||||
confidence: 0.7,
|
||||
uncertainties: Vec::new(),
|
||||
monitoring_action_workflow: None,
|
||||
};
|
||||
|
||||
let error = generate_scene_package(GenerateSceneRequest {
|
||||
@@ -3490,6 +3501,7 @@ fn generator_accepts_g8_local_doc_select_data_contract() {
|
||||
column_defs: Vec::new(),
|
||||
confidence: 0.7,
|
||||
uncertainties: Vec::new(),
|
||||
monitoring_action_workflow: None,
|
||||
};
|
||||
|
||||
let skill_root = generate_scene_package(GenerateSceneRequest {
|
||||
@@ -3695,6 +3707,192 @@ fn generator_emits_monitoring_template() {
|
||||
assert!(generated_manifest.contains("category = \"monitoring\""));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator_emits_monitoring_action_detect_preview_anchor_package() {
|
||||
let output_root = temp_workspace("sgclaw-monitoring-action-detect-preview");
|
||||
let skill_root =
|
||||
generate_monitoring_action_detect_preview_package(GenerateMonitoringActionPreviewRequest {
|
||||
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/monitoring_action_ir_contract_2026-04-21.json",
|
||||
),
|
||||
})
|
||||
.unwrap();
|
||||
|
||||
let scene_toml = fs::read_to_string(skill_root.join("scene.toml")).unwrap();
|
||||
assert!(scene_toml.contains("category = \"monitoring\""));
|
||||
assert!(scene_toml.contains("suffix = \""));
|
||||
assert!(scene_toml.contains("[safety]"));
|
||||
assert!(scene_toml.contains("dry_run_default = true"));
|
||||
assert!(scene_toml.contains("action_modes_enabled = false"));
|
||||
assert!(scene_toml.contains("mode = \"detect_preview\""));
|
||||
assert!(scene_toml.contains("repetCtrlSend"));
|
||||
|
||||
let generation_report =
|
||||
fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap();
|
||||
let generated_report: SceneIr = serde_json::from_str(&generation_report).unwrap();
|
||||
assert_eq!(
|
||||
generated_report.workflow_archetype(),
|
||||
WorkflowArchetype::MonitoringActionWorkflow
|
||||
);
|
||||
let monitoring = generated_report
|
||||
.monitoring_action_workflow
|
||||
.as_ref()
|
||||
.expect("expected monitoring action workflow metadata");
|
||||
assert_eq!(monitoring.default_mode, "detect_preview");
|
||||
assert!(monitoring.side_effect_policy.dry_run_default);
|
||||
assert!(monitoring
|
||||
.side_effect_policy
|
||||
.blocked_call_signatures
|
||||
.iter()
|
||||
.any(|item| item == "repetCtrlSend"));
|
||||
|
||||
let script = fs::read_to_string(skill_root.join("scripts/detect_preview.js")).unwrap();
|
||||
assert!(script.contains("monitoring_action_workflow"));
|
||||
assert!(script.contains("detect_preview"));
|
||||
assert!(!script.contains("repetCtrlSend("));
|
||||
assert!(!script.contains("mac.sendMessages("));
|
||||
assert!(!script.contains("mac.callOutLogin("));
|
||||
assert!(!script.contains("mac.audioPlay("));
|
||||
assert!(!script.contains("mac.exeTQueue("));
|
||||
|
||||
let blocked =
|
||||
fs::read_to_string(skill_root.join("references/blocked-side-effects.json")).unwrap();
|
||||
assert!(blocked.contains("repetCtrlSend"));
|
||||
|
||||
let test_status = std::process::Command::new("node")
|
||||
.arg("detect_preview.test.js")
|
||||
.current_dir(skill_root.join("scripts"))
|
||||
.status()
|
||||
.unwrap();
|
||||
assert!(test_status.success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator_emits_scheduled_monitoring_action_skill_package() {
|
||||
let output_root = temp_workspace("sgclaw-scheduled-monitoring-action-skill");
|
||||
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();
|
||||
|
||||
for relative in [
|
||||
"SKILL.toml",
|
||||
"SKILL.md",
|
||||
"scene.toml",
|
||||
"scripts/detect.js",
|
||||
"scripts/decide.js",
|
||||
"scripts/action_plan.js",
|
||||
"scripts/detect.test.js",
|
||||
"references/source-evidence.json",
|
||||
"references/workflow-ir.json",
|
||||
"references/trigger-contract.json",
|
||||
"references/platform-dependencies.json",
|
||||
"references/side-effect-policy.json",
|
||||
"references/audit-policy.json",
|
||||
"references/idempotency-policy.json",
|
||||
"references/generation-report.json",
|
||||
] {
|
||||
assert!(
|
||||
skill_root.join(relative).exists(),
|
||||
"expected scheduled skill package file {relative}"
|
||||
);
|
||||
}
|
||||
|
||||
let scene_toml = fs::read_to_string(skill_root.join("scene.toml")).unwrap();
|
||||
toml::from_str::<toml::Value>(&scene_toml).unwrap();
|
||||
assert!(scene_toml.contains("kind = \"scheduled_monitoring_action_workflow\""));
|
||||
assert!(scene_toml.contains("natural_language_primary = false"));
|
||||
assert!(scene_toml.contains("enabled = [\"dry_run\", \"monitor_only\"]"));
|
||||
assert!(scene_toml.contains("disabled = [\"active\", \"queue_process\"]"));
|
||||
assert!(scene_toml.contains("active_enabled = false"));
|
||||
assert!(scene_toml.contains("queue_process_enabled = false"));
|
||||
assert!(!scene_toml.contains("[deterministic]"));
|
||||
|
||||
let skill_toml = fs::read_to_string(skill_root.join("SKILL.toml")).unwrap();
|
||||
toml::from_str::<toml::Value>(&skill_toml).unwrap();
|
||||
assert!(skill_toml.contains("name = \"detect\""));
|
||||
assert!(skill_toml.contains("name = \"decide\""));
|
||||
assert!(skill_toml.contains("name = \"action_plan\""));
|
||||
assert!(skill_toml.contains("kind = \"scheduled_monitoring_action_workflow\""));
|
||||
|
||||
let generation_report =
|
||||
fs::read_to_string(skill_root.join("references/generation-report.json")).unwrap();
|
||||
let report: serde_json::Value = serde_json::from_str(&generation_report).unwrap();
|
||||
assert_eq!(report["family"], "scheduled_monitoring_action_workflow");
|
||||
assert_eq!(report["naturalLanguagePrimary"], false);
|
||||
assert_eq!(report["safety"]["activeEnabled"], false);
|
||||
assert_eq!(report["safety"]["queueProcessEnabled"], false);
|
||||
|
||||
let side_effect_policy =
|
||||
fs::read_to_string(skill_root.join("references/side-effect-policy.json")).unwrap();
|
||||
let side_effect_policy: serde_json::Value = serde_json::from_str(&side_effect_policy).unwrap();
|
||||
assert!(side_effect_policy["blockedCallSignatures"]
|
||||
.as_array()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.any(|item| item == "repetCtrlSend"));
|
||||
|
||||
let enabled_scripts = [
|
||||
fs::read_to_string(skill_root.join("scripts/detect.js")).unwrap(),
|
||||
fs::read_to_string(skill_root.join("scripts/decide.js")).unwrap(),
|
||||
fs::read_to_string(skill_root.join("scripts/action_plan.js")).unwrap(),
|
||||
];
|
||||
assert!(enabled_scripts[0].contains("platformServiceBaseUrl"));
|
||||
assert!(enabled_scripts[0].contains("postViaPageAxios"));
|
||||
assert!(enabled_scripts[0].contains("getViaPageAxios"));
|
||||
assert!(enabled_scripts[0].contains("EmssLib.dataEncrypt_PUB"));
|
||||
assert!(enabled_scripts[0].contains("readStepTraces"));
|
||||
assert!(enabled_scripts[0].contains("read_step_timeout_ms"));
|
||||
assert!(enabled_scripts[0].contains("scheduled monitoring read step"));
|
||||
for script in enabled_scripts {
|
||||
for forbidden in [
|
||||
"repetCtrlSend(",
|
||||
"mac.sendMessages(",
|
||||
"mac.callOutLogin(",
|
||||
"mac.audioPlay(",
|
||||
"mac.exeTQueue(",
|
||||
"_this.autoTask(",
|
||||
"_this.processQueue(",
|
||||
"setDisposeLog(",
|
||||
"setMonitorData(",
|
||||
"setMonitorLog(",
|
||||
"setSendMessageLog(",
|
||||
"setAudioPlayLog(",
|
||||
] {
|
||||
assert!(
|
||||
!script.contains(forbidden),
|
||||
"enabled scheduled script must not contain forbidden executable call {forbidden}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
let test_status = std::process::Command::new("node")
|
||||
.arg("detect.test.js")
|
||||
.current_dir(skill_root.join("scripts"))
|
||||
.status()
|
||||
.unwrap();
|
||||
assert!(test_status.success());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn generator_preserves_localhost_dependency_as_host_runtime_evidence() {
|
||||
let analysis = analyze_scene_source(Path::new(
|
||||
@@ -3850,6 +4048,7 @@ fn build_multi_mode_scene_ir() -> SceneIr {
|
||||
column_defs: Vec::new(),
|
||||
confidence: 0.9,
|
||||
uncertainties: Vec::new(),
|
||||
monitoring_action_workflow: None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3975,6 +4174,7 @@ fn build_paginated_scene_ir() -> SceneIr {
|
||||
column_defs: Vec::new(),
|
||||
confidence: 0.9,
|
||||
uncertainties: Vec::new(),
|
||||
monitoring_action_workflow: None,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user