feat(compile)!: trigger filter IR with data-driven Python evaluator#345
Conversation
🔍 Rust PR ReviewSummary: Has concerns — two correctness/security issues need addressing before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
🔍 Rust PR ReviewSummary: Good architectural design with two bugs that need fixes before merging — one logic error in API error handling and one bash quoting issue that will cause silent failures. Findings🐛 Bugs / Logic Issues1. The bash condition is: if [ -z "$PR_DATA" ] || echo "$PR_DATA" | python3 -c "..."; [ $? -ne 0 ] 2>/dev/null; thenTrace through the empty-PR_DATA case:
The warning is therefore only emitted when PR_DATA is non-empty AND invalid JSON. When The intended logic is if [ -z "$PR_DATA" ] || ! echo "$PR_DATA" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
echo "##[warning]Failed to fetch PR data from API — skipping API-based filters"
fi2. Python list double-quotes break bash string in Patterns are wrapped in Rust FILES_MATCH=$(echo "$CHANGED_FILES" | python3 -c "
import sys, fnmatch
includes = ["src/**/*.rs"] ← inner " closes the outer bash " here
...
" 2>/dev/null || echo 'true')The bash double-quote wrapping the entire Fix: use single-quoted Python strings 🔒 Security Concerns3. Label names with spaces are silently split in
for REQUIRED_LABEL in run-agent my label; do ← "my" and "label" are separate iterationsA user-specified label
|
🔍 Rust PR ReviewSummary: Solid architecture with well-structured code, but three bugs and one security concern worth addressing before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
🔍 Rust PR ReviewSummary: needs changes — two command injection vulnerabilities and three correctness bugs in the generated bash, all confirmed with PoCs. Findings🔒 Security Concerns1.
for REQUIRED_LABEL in run-agent $(id); do # $(id) executes!A label value of Fix: Quote the loop variable and use a bash array, or switch to a newline-delimited approach: REQUIRED_LABELS="run-agent
needs-review"
while IFS= read -r REQUIRED_LABEL; do ...
done <<< "$REQUIRED_LABELS"2. Changed-file patterns (with # pattern = "$(id)" passes shell_escape unchanged
FILES_MATCH=$(echo "$CHANGED_FILES" | python3 -c "
includes = ["$(id)"] # bash expands $(id) here
...Confirmed: Fix: Write patterns to a temp file or pass them via environment variables, never via interpolation into a double-quoted 🐛 Bugs / Logic Issues3. The Rust format string START_H=${09:00%%:*} # NOT a valid variable expansion
```
`${09:00%%:*}` treats `$9` (positional param) with colon operators — it does not parse the time string. In practice `START_H` is set to empty, and `$((10#$START_H * 60))` then aborts with `invalid integer constant`.
Confirmed:
```
bash: line 7: 10#: invalid integer constant
START_MINUTES=Fix: Assign the literal time to a named shell variable first, then apply parameter expansion: _START_TIME="{start}"
START_H=${_START_TIME%%:*}
START_M=${_START_TIME##*:}4. The generated bash is: if [ -z "$PR_DATA" ] || echo "$PR_DATA" | python3 ...; [ $? -ne 0 ] 2>/dev/null; then
echo "##[warning]Failed to fetch PR data..."
fiWhen Confirmed — running the exact generated condition with Fix: if [ -z "$PR_DATA" ] || ! echo "$PR_DATA" | python3 -c "import sys,json; json.load(sys.stdin)" 2>/dev/null; then
```
**5. `src/compile/pr_filters.rs:generate_change_count_check` — `grep -c . || echo '0'` produces double output**
When `CHANGED_FILES` is empty, `grep -c .` exits 1 **and** outputs `0` to stdout, then `|| echo '0'` also fires, producing `FILE_COUNT="0\n0"`. The subsequent `[ "$FILE_COUNT" -ge N ]` fails with `integer expression expected`.
Confirmed:
```
FILE_COUNT=[0
0]
bash: [: 0↵0: integer expression expectedFix: Rely on grep's own zero-count output; suppress the error exit without adding a second FILE_COUNT=$(echo "$CHANGED_FILES" | grep -c . 2>/dev/null; true)Or use ✅ What Looks Good
|
Add triggers.pr front matter field with native ADO branch/path config and runtime filter evaluation via gate steps in the Setup job. Phase 1 (Tier 1 filters): - PrTriggerConfig, PrFilters, PatternFilter, IncludeExcludeFilter, LabelFilter, BranchFilter, PathFilter types in types.rs - Gate step generation with title, author, source-branch, target-branch filters using ADO pipeline variables - Self-cancel via ADO REST API when filters don't match (cancelled builds are invisible to DownloadPipelineArtifact, preserving memory chain) - Build tag diagnostics (pr-gate:passed, pr-gate:skipped, pr-gate:<filter>-mismatch) and task warnings for filter failures - Agent job condition: non-PR builds bypass gate, PR builds require prGate.SHOULD_RUN=true - triggers.pr overrides schedule/pipeline trigger suppression - Native ADO pr: block emitted for branch/path filters Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move PR trigger filter logic from common.rs to pr_filters.rs: - generate_native_pr_trigger(), generate_pr_gate_step(), shell_escape(), add_condition_to_steps() and all associated tests - Add Tier 2 filter generators: labels (any-of/all-of/none-of), draft (isDraft check), changed-files (iteration changes API + fnmatch) - REST API preamble only emitted when Tier 2 filters are configured (has_tier2_filters() helper) - 12 new Tier 2 tests (27 total in module) common.rs reduced by ~450 lines, now delegates to pr_filters::. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…d-reason, expression) Add advanced pre-activation gate filters: - time-window: only run during a UTC time range (handles overnight windows) - min-changes / max-changes: gate on number of changed files - build-reason: include/exclude by Build.Reason (PullRequest, Manual, etc.) - expression: raw ADO condition expression escape hatch, ANDed into Agent job condition at compile time Types added to PrFilters: TimeWindowFilter, min_changes, max_changes, build_reason, expression. Shell escape updated to allow colon for time format. generate_agentic_depends_on now accepts optional expression. 13 new tests (40 total in pr_filters module). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
BREAKING CHANGE: top-level schedule: and triggers: keys are replaced by
a single on: key, aligning with gh-aw's on: syntax.
Migration:
schedule: daily → on: { schedule: daily }
triggers: → on:
pipeline: ... pipeline: ...
pr: ... pr: ...
Changes:
- OnConfig struct replaces TriggerConfig, absorbs ScheduleConfig
- FrontMatter convenience methods: schedule(), has_schedule(),
pipeline_trigger(), pr_trigger(), pr_filters()
- PipelineTrigger gains filters: Option<PipelineFilters> for
pipeline-specific gate filters (time-window, source-pipeline,
branch, build-reason, expression)
- PrFilters gains commit-message filter (Build.SourceVersionMessage)
- All fixtures, tests, and reference sites updated
- 1042 tests pass
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- commit-message filter: regex on Build.SourceVersionMessage (e.g., skip [skip-agent] in commit messages) - 3 new tests: commit-message gate step, on: config deserialization (simple + full with schedule/pipeline/pr) - Update schedule-syntax.md to reference on.schedule key Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Introduces a typed intermediate representation (IR) for trigger filter expressions, replacing the manual bash string construction in pr_filters.rs. The IR separates data acquisition (Fact) from boolean predicates (Predicate), enabling: - Compile-time conflict detection (impossible/redundant filter combinations) - Dependency-ordered fact acquisition (pipeline vars → API → computed) - A single codegen pass from IR → bash gate step - Shared infrastructure for both PR and pipeline completion triggers Filter compilation now follows a three-pass architecture: 1. Lower: PrFilters/PipelineFilters → Vec<FilterCheck> 2. Validate: detect conflicts (min>max, include/exclude overlap, zero-width time windows, label set contradictions) 3. Codegen: GateContext + Vec<FilterCheck> → bash gate step Pipeline completion triggers now support runtime filters via gate steps (GateContext::PipelineCompletion), using the same IR and codegen as PR filters. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
28d706d to
ceb32cd
Compare
🔍 Rust PR ReviewSummary: Needs changes — the IR architecture is solid but three correctness bugs and one security concern need attention before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
…ator The filter IR codegen now produces a JSON gate spec + embeds a generic Python evaluator, rather than constructing bash strings per-predicate. Architecture: - Bash is a thin ADO-macro shim (exports pipeline vars, passes base64 spec via env, invokes python3 heredoc) - Python evaluator owns all runtime logic: bypass, fact acquisition, predicate evaluation, result reporting, self-cancel - Gate spec is base64-encoded to prevent ADO macro expansion in the JSON payload New types: GateSpec, FactSpec, CheckSpec, PredicateSpec (serde Serialize) New methods: Fact::ado_exports(), Fact::kind(), build_gate_spec() New file: src/data/gate-eval.py (embedded via include_str!) New file: docs/filter-ir.md (IR specification) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Strong architectural improvement with two real bugs that need fixing before merge. Findings🐛 Bugs / Logic Issues
|
Introduces a CompilerExtension that controls the download and execution of the gate evaluator script for complex (Tier 2/3) trigger filters. Key changes: - New setup_steps() trait method on CompilerExtension for Setup job injection (distinct from prepare_steps() which injects into Execution) - TriggerFiltersExtension activates when filters require API calls or computed values (labels, draft, changed-files, time-window, min/max) - Extension generates: download step + gate step referencing external script at /tmp/ado-aw-scripts/gate-eval.py - compile_gate_step_external(): gate step that references a script path instead of inlining via heredoc - compile_gate_step_inline(): Tier 1 self-contained bash gate (reserved for future use when only pipeline-variable filters are configured) - needs_evaluator(): determines if checks require the Python evaluator - gate-eval.py moved to scripts/ for release artifact distribution - generate_setup_job() now accepts extensions parameter Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
- filter-ir.md: document TriggerFiltersExtension, Tier 1 inline path, scripts distribution, updated adding-new-filters guide - extending.md: document setup_steps() trait method and its distinction from prepare_steps() Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid architecture with well-structured IR, but has one functional bug, one silent data-loss bug, and a security gap in the Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
…evaluator - setup_steps() now receives &CompileContext (matching validate/mcpg_servers) - TriggerFiltersExtension uses env!(CARGO_PKG_VERSION) for download URL, no longer stores version field - compile_gate_step() heredoc path removed — only external and inline paths remain - Release workflow packages scripts/ as scripts.zip artifact with checksum - generate_setup_job() receives &CompileContext for extension forwarding Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid architecture, but has two correctness bugs that would cause silent runtime misbehavior, one broken bash condition, and a couple of error-handling gaps worth fixing before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
…er-filters generate_setup_job() now collects setup_steps() from every extension, not just when TriggerFiltersExtension is detected by name. The Tier 1 inline gate fallback only activates when no extension provided steps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Well-designed IR architecture with solid security fundamentals, but two correctness bugs need addressing before merge. Findings🐛 Bugs / Logic Issues
|
Integration tests (compiler_tests.rs): - Tier 1 filter fixture: inline bash gate step compilation + YAML validity - Tier 2 filter fixture: extension-based gate with download + YAML validity - Pipeline filter fixture: pipeline resource + gate step + YAML validity - PR filter agent depends-on + native PR trigger assertions Python evaluator tests (gate_eval_tests.py, 37 tests): - All 11 predicate types: regex, equality, value-in/not-in-set, numeric range, time window (inc. overnight), label set match, file glob, and/or/not - predicate_facts() helper coverage JSON Schema (schemars): - Derive JsonSchema on GateSpec/PredicateSpec types - generate_gate_spec_schema() → scripts/gate-spec.schema.json - Schema validation tests in filter_ir.rs Documentation fixes: - AGENTS.md: add filter_ir.rs, pr_filters.rs, trigger_filters.rs, scripts/ to architecture tree - extending.md: complete CompilerExtension trait listing (was missing 5 methods: setup_steps, allowed_copilot_tools, required_pipeline_vars, required_awf_mounts, awf_path_prepends) + add phase ordering note Bug fix: - generate_agentic_depends_on condition indentation (was double-indented by replace_with_indent) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Good architectural direction, but has one exploitable security bug and two logic errors that need addressing before merge. Findings🔒 Security Concerns
🐛 Bugs / Logic Issues
|
…_on signatures Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Move ADO variable mappings from inline bash exports to the step's env: block — the idiomatic ADO pattern. Avoids shell quoting issues and matches how SYSTEM_ACCESSTOKEN is already passed. The gate step is now a single-line bash command (python3 <path>) with all variables declared in env:. No more multiline bash heredoc. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Evaluator now reads SYSTEM_ACCESSTOKEN directly (the standard ADO secret variable name) instead of a custom ADO_SYSTEM_ACCESS_TOKEN alias. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid architectural foundation with a few correctness bugs, one test that writes to the source tree, and a missed error propagation pattern. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
🔍 Rust PR ReviewSummary: Strong architecture with solid test coverage, but there are two bugs that will cause user-visible failures (docs/deserialization mismatch), one security gap, and one supply chain concern. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
…ping
Documentation fixes:
- front-matter.md: all PatternFilter examples updated to bare string
glob syntax (was using removed {match: ...} object form + regex)
- filter-ir.md: all RegexMatch/regex_match references updated to
GlobMatch/glob_match
Security:
- expression escape hatch now validated against ADO expressions
via contains_ado_expression() — blocks macro injection
ADO branch prefix handling:
- gate-eval.py strips refs/heads/, refs/tags/, refs/pull/ from branch
fact values so patterns like 'feature/*' match naturally
- Pattern side also stripped so 'refs/heads/feature/*' matches too
- 5 new Python tests for ref prefix stripping
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Well-architected feature with one genuine behavioral bug in the Python evaluator, a policy deviation on Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
1. gate-eval.py: fail_open_facts now propagates to dependents — when changed_files fails open, changed_file_count also fails open instead of silently computing 0 and blocking min-changes checks 2. gate-eval.py: assert on unknown failure_policy values to surface schema drift early (was silently defaulting to fail_closed) 3. common.rs: remove dead has_filters intermediate binding — inlined directly into has_gate 4. filter_ir.rs: document SYSTEM_ACCESSTOKEN usage (always needed for self-cancel, uses built-in pipeline token) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Needs changes — strong architecture and security design, but there's a critical YAML indentation bug that will cause all generated pipelines to fail in Azure DevOps. Findings🐛 Bugs / Logic Issues[ Extension steps returned from jobs:
- job: Setup
steps:
- checkout: self
- bash: | # ← at jobs: level, not inside Setup's steps:
mkdir -p /tmp/ado-aw-scripts
- bash: python3 '/tmp/ado-aw-scripts/gate-eval.py'
name: prGateBoth the download step and the gate step are emitted as siblings of Fix: pass [
[ The function gets the last iteration's ID and fetches only that diff. For a PR with multiple iterations (force pushes, new commits), files changed in earlier iterations are invisible to 🔒 Security Concerns[ The gate step unconditionally injects
|
…ce_with_indent
Extension setup_steps() were emitted at the wrong YAML nesting level —
as siblings of the Setup job instead of inside its steps: list. This
caused ADO to reject the generated pipeline.
Fix: use {{ ext_setup_steps }} and {{ user_setup_steps }} markers in the
Setup job template, with replace_with_indent handling indentation from
the marker column position. User steps use format_steps_yaml_indented
with base_indent=0 (marker adds the 4-space indent).
Added structural integration test that parses the compiled YAML and
verifies gate steps are inside the Setup job's steps sequence (not
siblings of the job).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid architecture with good security foundations — three focused findings worth addressing before merge. Findings🔒 Security Concerns
🐛 Bugs / Logic Issues
|
1. CompilerExtension::setup_steps() now returns Result<Vec<String>> instead of Vec<String> — errors propagate via ? instead of .expect() panics. Trait signature, dispatch macro, extension impl, and generate_setup_job caller all updated. 2. Remove dead code flagged by clippy: - Unused import PrFilters in pr_filters.rs - #[allow(dead_code)] on And/Or/Not Predicate variants (reserved) 3. Document _fetch_changed_files iteration behavior: returns last iteration diff only (current state, not cumulative PR history). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1. gate-eval.py: extract shared _glob() helper used by both glob_match and file_glob_match — brackets are now literal everywhere (no more fnmatch character class behavior for changed-files patterns) 2. gate-eval.py: replace assert with raise ValueError for policy validation — assert is stripped by python -O 3. docs/network.md: document System.AccessToken usage in trigger filter gate step as a deliberate scoped exception (Setup job only, not passed to agent or executor) 4. filter_ir.rs: remove duplicate ADO_BUILD_REASON from Fact::BuildReason.ado_exports() — already in infra vars Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Well-architected feature — the three-pass IR, data-driven evaluator, and env-block injection are all sound. A few correctness issues worth addressing before merge. Findings🐛 Bugs / Logic Issues
|
…args
1. TriggerFiltersExtension::is_needed() now checks lowered checks are
non-empty — filters: {} no longer activates the extension or
produces a dangling prGate condition reference
2. setup_steps() only emits the download step when gate steps exist
3. gate-eval.py: _glob() no longer strips ref prefixes from patterns
unconditionally — only branch-related facts (source_branch,
target_branch, triggering_branch) get ref prefix stripping
4. generate_agentic_depends_on() accepts &[&str] instead of
Option<&str> for expressions — each expression is pushed as a
separate and() argument instead of comma-joining into one string
5. gate-eval.py: case-sensitive glob matching documented (title:
"*review*" won't match "Review")
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid architecture with one concrete bug and a few security/design concerns worth addressing before merge. Findings🐛 Bugs / Logic Issues
if steps_parts.is_empty() && ext_steps_combined.is_empty() {
return Ok(String::new()); // no Setup job generated
}This fires when there are no user Fix: base let has_pr_filters = pr_filters.map(|f| !lower_pr_filters(f).is_empty()).unwrap_or(false);
let has_pipeline_filters = pipeline_filters.map(|f| !lower_pipeline_filters(f).is_empty()).unwrap_or(false);🔒 Security Concerns
step.push_str(" SYSTEM_ACCESSTOKEN: $(System.AccessToken)\n");The stored project convention is: "ADO tokens come from ARM service connections; Beyond convention, this requires the "Allow scripts to access the OAuth token" setting to be enabled on the pipeline — this is Fail-open for unknown predicate types in log(f"##[warning]Unknown predicate type: {t}")
return True # ← gates pass silentlyIf the installed
|
has_pr_filters and has_pipeline_filters are now derived from
lower_*_filters().is_empty() instead of Option::is_some(). This
ensures that `filters: {}` (empty object with all None fields)
does not activate the gate condition in generate_agentic_depends_on,
which would create a dependsOn: Setup reference to a job that was
never emitted.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Well-architected feature — a few issues worth addressing before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
…uard 1. types.rs: add comment explaining test_pr_trigger_config_* tests use triggers: key for raw OnConfig deserialization (not the on: FrontMatter rename) 2. gate-eval.py: _glob uses re.DOTALL flag so * matches newlines (edge case: PR titles with embedded newlines from ADO API) 3. docs/front-matter.md: document System.AccessToken requirement — "Allow scripts to access the OAuth token" must be enabled, and what happens when it isn't (build succeeds instead of cancelling) 4. pr_filters.rs: expanded comment on filters-only empty pr: return explaining the ADO implication (trigger on all PRs, gate handles filtering) and warning against changing to pr: none Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Solid architecture with well-designed IR, good injection defences on the hot path — but two correctness/security issues need fixing before merge. Findings🐛 Bugs / Logic Issues
🔒 Security Concerns
|
…e condition
Security:
- Reject newline characters in expression escape hatch values. A
crafted expression with embedded newlines could terminate the YAML
literal block scalar and inject arbitrary job-level keys (pool,
steps, etc.). Now validated via contains_newline() before building
the condition YAML.
Bug:
- generate_setup_job: has_gate now computed from lower_*_filters
emptiness check (same as is_needed and compile_shared). Empty
filters:{} no longer conditions setup steps on a non-existent
prGate.SHOULD_RUN variable.
Improvement:
- Gate step now has explicit condition: succeeded() instead of
relying on ADO's implicit default.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Strong architecture and well-tested, but has one correctness bug and two security concerns worth addressing before merge. Findings🐛 Bugs / Logic Issues
let condition = match (pr_filters.is_some(), pipeline_filters.is_some()) {
(true, true) => "and(eq(variables['prGate.SHOULD_RUN'], 'true'), eq(variables['pipelineGate.SHOULD_RUN'], 'true'))"
...
};The guard on:
pr:
filters: {} # Some(PrFilters::default()) — no checks after lowering
pipeline:
name: Build
filters:
source-pipeline: "Build*" # produces checksHere Fix: thread
if has_pr_trigger {
return super::pr_filters::generate_native_pr_trigger(
on_config.as_ref().unwrap().pr.as_ref().unwrap()
);
}Technically safe (same condition was just asserted), but fragile to future refactors. Idiomatic fix: if let Some(pr) = on_config.as_ref().and_then(|o| o.pr.as_ref()) {
return super::pr_filters::generate_native_pr_trigger(pr);
}🔒 Security Concerns
step.push_str(" SYSTEM_ACCESSTOKEN: $(System.AccessToken)\n");
```
This repo's established convention is that ADO tokens come from ARM service connections — `System.AccessToken` is not used directly. The gate step uses it for both self-cancel (write, PATCH `/builds/{id}`) and PR metadata/changed-files (read, GET). This requires consuming pipelines to enable **"Allow scripts to access the OAuth token"** in pipeline settings — a non-default and undocumented requirement that will silently fail (empty token, REST 401) for any pipeline that doesn't have it set.
The comment in the code acknowledges this, but there's no corresponding documentation in `docs/front-matter.md` or compilation error/warning when the `filters:` block is present. Consider emitting a compile-time warning (or adding a required pipeline variable guard in the gate step) so authors know this setting must be configured.
---
**`scripts.zip` downloaded without integrity verification**
`src/compile/extensions/trigger_filters.rs`:
```
curl -fsSL "{RELEASE_BASE_URL}/v{version}/scripts.zip" -o /tmp/ado-aw-scripts/scripts.zipThe version is compile-time pinned (good — no version drift), but there is no checksum verification. Suggested mitigation: embed the expected SHA256 for echo "{expected_sha256} /tmp/ado-aw-scripts/scripts.zip" | sha256sum --check✅ What Looks Good
|
…ove double unwrap
1. generate_setup_job: condition match now uses has_pr_gate/has_pipeline_gate
(derived from lower_*_filters emptiness) instead of pr_filters.is_some().
Empty filters:{} with a non-empty pipeline filter no longer references
a non-existent prGate.SHOULD_RUN variable.
2. generate_pr_trigger: replace double .unwrap() chain with idiomatic
if-let pattern for safety against future refactors.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
🔍 Rust PR ReviewSummary: Well-architected change with strong injection defenses — three targeted concerns worth addressing before merge. Findings🔒 Security Concerns1. The generated gate step injects At a minimum, the docs/front-matter.md update should call this out as a prerequisite, and the compiler should produce a warning or the generated YAML should include a comment. 2. No integrity verification on the downloaded curl -fsSL "https://github.com/githubnext/ado-aw/releases/download/v{version}/scripts.zip" -o ...The download has no SHA-256 checksum verification. The pipeline runs in the AWF network-isolated sandbox, so the risk is bounded — but a compromised release artifact or a DNS-level response substitution would silently replace echo "<sha256_hash> /tmp/ado-aw-scripts/scripts.zip" | sha256sum -c3. Missing
yaml.push_str(&format!(" - '{}'\n", b.replace('\'', "''")));If for branch in &pr.branches.include {
validate::reject_pipeline_injection(branch, "on.pr.branches.include")?;
}
|
|
@copilot Missing reject_pipeline_injection for on.pr branch/path values on.pipeline.name/project/branches are validated with reject_pipeline_injection. on.pr.branches.include/exclude and on.pr.paths.include/exclude are not — they only go through sanitize_config_fields(). In generate_native_pr_trigger, these values are embedded using ' → '' YAML escaping only: yaml.push_str(&format!(" - '{}'\n", b.replace(''', "''"))); for branch in &pr.branches.include { |
Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/2081cc8e-ded5-4a35-93e9-f932889d7760 Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
…essages Agent-Logs-Url: https://github.com/githubnext/ado-aw/sessions/2081cc8e-ded5-4a35-93e9-f932889d7760 Co-authored-by: jamesadevine <4742697+jamesadevine@users.noreply.github.com>
Fixed in 48b8439. Added Warning Firewall rules blocked me from connecting to one or more addresses (expand for details)I tried to connect to the following addresses, but was blocked by firewall rules:
If you need me to access, download, or install something from one of these locations, you can either:
|
Summary
Adds a complete trigger filter compilation system to ado-aw. Pipeline authors can configure runtime filters under
on.pr.filtersandon.pipeline.filtersthat evaluate at pipeline runtime and self-cancel the build if conditions aren't met.This is a breaking change — the
triggers:top-level key is renamed toon:, andschedule:moves underon:.Architecture
Three-Pass Filter IR (
src/compile/filter_ir.rs)Filter compilation follows a formal IR pipeline:
PrFilters/PipelineFilters→Vec<FilterCheck>(typed predicates over typed facts)GateContext+Vec<FilterCheck>→ JSON gate spec + bash step referencing external evaluatorData-Driven Python Evaluator (
scripts/gate-eval.py)All filter logic lives in a generic Python evaluator, not inline bash. The compiled pipeline YAML contains:
scripts.zipfrom the ado-aw releaseenv:block and invokespython3 gate-eval.pyThe evaluator handles: build-reason bypass, fact acquisition (env vars, REST API, datetime), predicate evaluation (glob, equality, set membership, numeric range, time window, labels, file globs, and/or/not), failure policies, ADO logging commands, and self-cancel.
TriggerFiltersExtension (
src/compile/extensions/trigger_filters.rs)A
CompilerExtensionthat activates when anyfilters:configuration is present. Uses the newsetup_steps()trait method to inject into the Setup job (distinct fromprepare_steps()which injects into the Execution job). Handles validation, download step generation, and gate step generation.JSON Schema (
scripts/gate-spec.schema.json)Generated from Rust types via
schemars— the formal contract between the compiler and the Python evaluator.Front Matter Syntax
Glob patterns (
*any chars,?single char) — no regex escaping needed:Migration
Key Design Decisions
title: "*[review]*"nottitle: { match: "\\[review\\]" }. Aligned with gh-aw's exact-match approach.env:block, preventing injection via PR title/branch values.gate-eval.pyships alongside ado-aw binary, downloaded from deterministic version URL.Files Changed
New files
src/compile/filter_ir.rs— Filter IR: Fact, Predicate, spec types, lowering, validation, codegensrc/compile/extensions/trigger_filters.rs— TriggerFiltersExtensionscripts/gate-eval.py— Python gate evaluatorscripts/gate-spec.schema.json— JSON Schema for gate specdocs/filter-ir.md— IR specificationtests/gate_eval_tests.py— 40 Python evaluator teststests/fixtures/pr-filter-tier1-agent.md— Tier 1 filter fixturetests/fixtures/pr-filter-tier2-agent.md— Tier 2 filter fixturetests/fixtures/pipeline-filter-agent.md— Pipeline filter fixtureModified files
src/compile/pr_filters.rs— reduced to native PR trigger + helpers (gate generation moved to extension)src/compile/common.rs—generate_setup_job()accepts extensions + context,generate_agentic_depends_on()handles dual gatessrc/compile/types.rs—PatternFilter(glob,#[serde(transparent)]),pipeline_filters()accessorsrc/compile/extensions/mod.rs—setup_steps()trait method,TriggerFiltersvariant in Extension enum.github/workflows/release.yml— uploadsscripts.zipas release artifactAGENTS.md— architecture tree updateddocs/front-matter.md—on:syntax, validation errors, filter behavior notesdocs/extending.md— complete trait listing,setup_steps()docsTest Coverage