1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
//! PMAT-160 (#467): `apply --check` selects before it checks.
//!
//! The `--check` arm of `apply_mode_exits` handed four of the operator's
//! arguments straight to `cmd_check` and returned — before `cmd_apply_scoped`,
//! and therefore before any selection ran at all. Measured on 1.25.2 with
//! `alpha -> bravo` converged and unrelated `charlie` red:
//!
//! ```text
//! apply --check --subset alpha FAIL charlie (local) — exit 1 rc=1
//! apply --check -g web FAIL charlie (local) — exit 1 rc=1
//! apply --check --skip charlie FAIL charlie (local) — exit 1 rc=1
//! ```
//!
//! Every scoping flag was inert: `--subset`, `--exclude`, `--skip`,
//! `--only-machine`, `--exclude-machine` and `--resource-filter` were not even
//! read on this path, and `-r`/`-g` were passed but as a plain id filter with
//! no closure. A scoped check that fails on a resource outside its own scope
//! reports the operator's stack as broken over something they excluded — and in
//! CI the exit code is the only thing read.
//!
//! So this branch does what the apply does, in the same order and with the same
//! `Selectors`: parse, resolve once, then check what came back. Lives in its own
//! file because `dispatch_apply_b.rs` is at the 500-line ceiling.
use super::apply_selection::{resolve_selection, Selectors};
use super::commands::ApplyArgs;
use std::path::Path;
/// The `ApplyScope` half of the operator's selectors (GH-211).
///
/// One reader for the four flags, shared with `apply_execute`, so the check and
/// the apply cannot drift apart about which flags scope a run.
pub(super) fn scope_of(args: &ApplyArgs) -> super::apply_scope::ApplyScope<'_> {
super::apply_scope::ApplyScope {
skip: args.skip.as_deref(),
only_machine: args.only_machine.as_deref(),
exclude_machine: args.exclude_machine.as_deref(),
resource_filter: args.resource_filter.as_deref(),
}
}
/// Every resource-set selector the operator typed, as one value.
///
/// `-m` is deliberately absent (it picks the executor, not the resource set);
/// `-t` is carried so the resolver can existence-check it, and applied again
/// per resource by the check loop, exactly as the planner applies it.
pub(super) fn selectors_of<'a>(
args: &'a ApplyArgs,
scope: &super::apply_scope::ApplyScope<'a>,
) -> Selectors<'a> {
Selectors {
resource: args.resource.as_deref(),
group: args.group.as_deref(),
subset: args.subset.as_deref(),
exclude: args.exclude.as_deref(),
tag: args.tag.as_deref(),
..Default::default()
}
.with_scope(scope)
}
/// `apply --check`: parse, resolve the selection, check the selected config.
///
/// Exit code is unchanged in meaning and narrower in scope: 0 when every
/// SELECTED check passes, even if an unselected resource is red.
pub(super) fn cmd_apply_check(
args: &ApplyArgs,
state_dir: &Path,
verbose: bool,
) -> Result<(), String> {
let mut config = super::helpers::parse_and_validate(&args.file)?;
let scope = scope_of(args);
resolve_selection(&mut config, &selectors_of(args, &scope), verbose)?;
super::check::cmd_check_selected(
&config,
args.machine.as_deref(),
args.tag.as_deref(),
state_dir,
args.json,
verbose,
)
}