6.5.1 Affected-Target Service Contract
An affected-target selector answers a narrower question than “what changed?”: for two repository states, a declared build universe, and a declared set of configurations, which requested roots might need validation? That answer can save substantial CI work, but only if the service can also say when it does not have enough evidence to answer safely. Treat target selection as a versioned service contract, not as a clever query that happens to print labels.
The Answer Is Meaningless Without Its Universe
Suppose a selector returns:
//app:binary
//app:test
The labels look precise, but they do not yet support a CI decision. You cannot tell which base and head revisions were compared, whether //app/... or the whole repository was examined, which platforms and flags were represented, or whether a missing target means “unaffected” or “not analyzed.”
Bazel's query tools provide useful graph evidence, but they answer different questions. query operates on the declared target graph, while cquery analyzes configured targets for declared roots and options; transitions can produce several configured instances of one label.1 The choice can change both the cost and the coverage of a selector; bazel-diff therefore exposes both query modes rather than pretending they are interchangeable.2 5.2.2 bazel cquery — Configured Graph develops that configured-target distinction, and 5.2.7 Query in CI Pipelines shows how to use both tools as bounded CI evidence.
A useful selector request names at least:
- the immutable base and head revision identities;
- the candidate roots or target patterns whose validation is being decided;
- the configurations, platforms, and toolchains the service claims to represent;
- the comparison mode and selector version;
- any repository state or generated snapshot needed to reproduce the comparison.
The universe is the combination of those boundaries. “Affected” never means affected everywhere; it means affected within this request's declared universe. This is the service-level extension of the bounded graph work in 5.3.2 Practical Graph Analysis.
Return a Decision, Not Just Labels
Use three outcomes:
| Outcome | Meaning | Safe consumer action |
|---|---|---|
affected | The available evidence connects one or more candidate roots to the change within the declared universe. | Validate the returned roots. |
unaffected | The service completed its supported comparison and found no affected candidate root. | Skip only the work covered by that universe. |
unknown | Required evidence is absent, stale, incompatible, or outside the service's supported model. | Do not interpret the result as permission to skip. |
An empty affected set and unknown are therefore opposites. The first is a completed negative result. The second says that no trustworthy negative result exists.
Represent that distinction explicitly in the response rather than encoding it through an empty file, a warning line, or a process exit status that every caller must interpret independently:
A versioned request declares base and head revisions, roots, configurations, and selector version. Versioned evidence is compared within that scope and yields one typed result: affected with roots and explanations, unaffected with completed evidence and an empty set, or unknown with an evidence gap and no negative claim. A separate CI policy boundary consumes the result without changing its technical meaning.
roots + explanation
targets: [] + complete
evidence gap · no negative claim
{
"outcome": "affected",
"base_revision": "<immutable-id>",
"head_revision": "<immutable-id>",
"universe": {
"roots": ["//app/..."],
"configurations": ["linux-x86_64-release"]
},
"targets": ["//app:binary", "//app:test"],
"selector": {
"implementation": "graph-hash",
"version": "<version>"
},
"explanations": [
{
"target": "//app:test",
"evidence": "depends on changed target //lib:parser"
}
]
}
This is an interface sketch, not a mandatory wire format. The important properties are typed outcome, immutable comparison identity, explicit scope, implementation identity, and evidence that a human or another service can inspect.
Make the Result Reproducible and Explainable
Target-selection systems commonly sit before the expensive part of CI. Tinder's published design uses graph hashes from two revisions to produce impacted targets, then feeds that output into a separate orchestration layer that schedules builds, tests, and artifacts.3 The separation is useful: the selector establishes impact evidence; the CI system decides what to run.
Preserve enough information to replay that boundary. A diagnostic record should answer:
- Which exact request produced this result?
- Which evidence connected each selected root to a changed input or target?
- Why was an expected-looking root omitted?
- Which unsupported or missing input would have changed the outcome to
unknown? - Which selector and schema versions interpreted the snapshots?
Do not promise one universal explanation shape. A reverse-dependency selector may report a path through the graph; a hash comparator may report old and new fingerprints; a metadata rule may report that a special file invalidated the ordinary calculation. The portable contract is that the explanation identifies the evidence and its scope, not that every implementation exposes the same algorithm.
bazel-diff is one concrete implementation: it generates hashes for two graphs and emits impacted targets, with query and cquery modes and optional seed files for changes that require broader rebuilding.4 It is evidence that an affected-target service can be built, not evidence that every selector has identical completeness or failure semantics. 6.5.2 Comparing Bazel Graphs Across Revisions continues with the mechanics of comparing base and head graphs.
Test the Contract at Its Boundaries
A selector test suite needs more than a fixture where one source edit selects one test. Begin with three contract cases:
- Known affected: a supported change reaches a candidate root; the response selects it and explains the connection.
- Known unaffected: a supported change is outside the candidate roots' modeled dependencies; the response is
unaffected, with the compared universe recorded. - Unknown: remove or corrupt required comparison evidence; the response is
unknown, never an empty successful result.
Run the affected-target-contract snippet to see those three cases through one deterministic selector interface. Its contract verifier checks the echoed identity envelope, the affected root's explanation, completed evidence for an empty unaffected result, and the missing-configuration gap that keeps unknown out of the empty-result path.
Then verify identity errors: swap the requested head revision, use a snapshot produced by another selector version, or omit one supported configuration. These cases test whether the service refuses to make a narrower claim than its evidence supports. 6.5.2 Comparing Bazel Graphs Across Revisions implements base/head comparison; 6.5.3 Global Target-Selection Invalidators classifies global invalidators; 6.5.4 Selecting Affected Targets Across Configurations proves configuration coverage; 6.5.5 Incomplete Selector Evidence detects deletes, renames, and stale evidence; and 6.5.6 Fail-Closed Target Selection defines the conservative response.
Do not store a selector response as if it were a Bazel action-cache result. An action cache key describes a Bazel action and its declared execution inputs. A selector response describes a comparison between repository states under a service-specific universe and algorithm. Both may use hashes, but their identities and correctness claims are different.
Keep Policy Outside the Technical Answer
The selector should report what its supported evidence establishes. It should not silently decide whether a team may skip integration tests, whether a merge queue may advance, or how much false-negative risk is acceptable. Those are consumer policies.
That boundary lets multiple workflows consume one technical result without changing its meaning. H.7.6 Speculation can use selection evidence when scheduling speculative work; H.7.3 Evidence Selection can define which test scopes a change must protect; and H.7.2 Evidence Portfolio can apply different cadences to different test layers. H.10.3 Service Capacity helps determine when the cost of broad validation makes such infrastructure worthwhile. None of those policies turns unknown into unaffected.
Classify: A selector successfully compared main@A with change@B for Linux, but the request also requires a Windows configuration that the snapshot does not contain. Should the response be unaffected for Windows because no affected Windows labels were found?
Reveal
No. The selector lacks required evidence for the declared universe, so the response is unknown. It may preserve the completed Linux evidence as diagnostic detail, but it cannot promote a partial negative result into permission to skip the whole request.
An affected-target result is a scoped, reproducible claim: immutable base and head revisions, candidate roots, configurations and platforms, selector identity, typed outcome, and supporting evidence travel together. affected and unaffected are completed answers within that universe; unknown is the explicit result when the evidence cannot justify either one. Keep selection evidence separate from the CI policy that decides what work may be skipped.
Check your understanding · 3 questions
1.Match each completed or incomplete comparison to the response outcome it justifies:
Drag each answer onto the matching prompt, or click an answer and then click a prompt
2.Which details must travel with an unaffected result so a consumer can treat its empty target list as a bounded, reproducible answer?
Select all that apply
3.A selector returns unknown because its snapshot identity does not match the request. What is the correct role for the CI consumer?
Select one answer
Footnotes
-
Configurable Query (cquery) — configured-target evaluation for declared roots and options, including multiple configurations reached through transitions ↩
-
Precision CI at Scale: Target-Aware Workflows with Bazel Diff - Maxwell Elliott & Connor Wybranowski — query and cquery modes and their precision/cost trade-off ↩
-
Precision CI at Scale: Target-Aware Workflows with Bazel Diff - Maxwell Elliott & Connor Wybranowski — separation of impact selection, orchestration, and dynamic CI pipelines ↩
-
bazel-diff — hash-based affected-target selection — two-revision graph hashing, query modes, seed files, and impacted-target output ↩