diff --git a/README.md b/README.md index 6ac9419c..b6db568d 100644 --- a/README.md +++ b/README.md @@ -81,7 +81,8 @@ description: "Checks for outdated dependencies and opens PRs" engine: id: copilot model: claude-sonnet-4.5 -schedule: weekly on monday around 9:00 +on: + schedule: weekly on monday around 9:00 pool: AZS-1ES-L-MMS-ubuntu-22.04 tools: azure-devops: true diff --git a/docs/front-matter.md b/docs/front-matter.md index 0a1fe5dc..3c5108d1 100644 --- a/docs/front-matter.md +++ b/docs/front-matter.md @@ -16,13 +16,14 @@ engine: copilot # Engine identifier. Defaults to copilot. Currently only 'copilo # id: copilot # model: claude-opus-4.7 # timeout-minutes: 30 -schedule: daily around 14:00 # Fuzzy schedule syntax - see docs/schedule-syntax.md -# schedule: # Alternative object format (with branch filtering) -# run: daily around 14:00 -# branches: -# - main -# - release/* -workspace: repo # Optional: "root", "repo" (alias: "self"), or a checked-out repository alias. If not specified, defaults to "root" when no additional repositories are listed in `checkout:`, and to "repo" when one or more additional repos are checked out. See "Workspace Defaults" below. +on: + schedule: daily around 14:00 # Fuzzy schedule syntax - see docs/schedule-syntax.md + # schedule: # Alternative object format (with branch filtering) + # run: daily around 14:00 + # branches: + # - main + # - release/* +workspace: repo# Optional: "root", "repo" (alias: "self"), or a checked-out repository alias. If not specified, defaults to "root" when no additional repositories are listed in `checkout:`, and to "repo" when one or more additional repos are checked out. See "Workspace Defaults" below. pool: AZS-1ES-L-MMS-ubuntu-22.04 # Agent pool name (string format). Defaults to AZS-1ES-L-MMS-ubuntu-22.04. # pool: # Alternative object format (required for 1ES if specifying os) # name: AZS-1ES-L-MMS-ubuntu-22.04 diff --git a/examples/azure-devops-mcp.md b/examples/azure-devops-mcp.md index fe65a2c2..7d9a576a 100644 --- a/examples/azure-devops-mcp.md +++ b/examples/azure-devops-mcp.md @@ -1,7 +1,8 @@ --- name: "ADO Work Item Triage" description: "Triages work items using the Azure DevOps MCP" -schedule: daily around 9:00 +on: + schedule: daily around 9:00 tools: azure-devops: toolsets: [core, work, work-items] diff --git a/examples/lean-verifier.md b/examples/lean-verifier.md index ebb46986..120a5e39 100644 --- a/examples/lean-verifier.md +++ b/examples/lean-verifier.md @@ -1,7 +1,8 @@ --- name: "Lean Formal Verifier" description: "Analyzes code and builds formal Lean 4 proofs of critical invariants" -schedule: weekly on friday around 17:00 +on: + schedule: weekly on friday around 17:00 tools: cache-memory: true runtimes: diff --git a/examples/sample-agent.md b/examples/sample-agent.md index cac3ced1..411baec8 100644 --- a/examples/sample-agent.md +++ b/examples/sample-agent.md @@ -1,7 +1,8 @@ --- name: "Daily Code Review" description: "Reviews code changes and creates summary reports" -schedule: daily +on: + schedule: daily repositories: - repository: azure-devops-agentic-pipelines type: git diff --git a/src/compile/types.rs b/src/compile/types.rs index cb200acd..2ba98a82 100644 --- a/src/compile/types.rs +++ b/src/compile/types.rs @@ -566,6 +566,7 @@ pub struct PipelineParameter { /// Front matter configuration from the input markdown file #[derive(Debug, Deserialize)] +#[serde(deny_unknown_fields)] pub struct FrontMatter { /// Agent name (required) pub name: String, @@ -1712,6 +1713,64 @@ Body assert!(result.is_err(), "unknown fields in network should be rejected"); } + // ─── FrontMatter deny_unknown_fields ───────────────────────────────────── + + #[test] + fn test_front_matter_rejects_unknown_top_level_field() { + let content = r#"--- +name: "Test" +description: "Test" +safeoutputs: + upload-pipeline-artifact: {} +--- + +Body +"#; + let result = super::super::common::parse_markdown(content); + assert!(result.is_err(), "unknown top-level field 'safeoutputs' should be rejected"); + let err = format!("{:#}", result.unwrap_err()); + assert!( + err.contains("unknown field `safeoutputs`"), + "error should mention unknown field `safeoutputs`, got: {}", + err + ); + } + + #[test] + fn test_front_matter_rejects_top_level_schedule() { + let content = r#"--- +name: "Test" +description: "Test" +schedule: daily around 14:00 +--- + +Body +"#; + let result = super::super::common::parse_markdown(content); + assert!(result.is_err(), "top-level 'schedule' should be rejected (use on.schedule)"); + let err = format!("{:#}", result.unwrap_err()); + assert!( + err.contains("unknown field `schedule`"), + "error should mention unknown field `schedule`, got: {}", + err + ); + } + + #[test] + fn test_front_matter_accepts_safe_outputs_with_hyphen() { + let content = r#"--- +name: "Test" +description: "Test" +safe-outputs: + upload-pipeline-artifact: {} +--- + +Body +"#; + let (fm, _) = super::super::common::parse_markdown(content).unwrap(); + assert!(fm.safe_outputs.contains_key("upload-pipeline-artifact")); + } + // ─── PrTriggerConfig deserialization ───────────────────────────────────── // NOTE: These tests use `triggers:` as a wrapper key and deserialize // OnConfig directly (not through FrontMatter). They test struct