check-target Action Reference¶
actions/check-target composes resolve-baseline +
collect-facts + the root abicheck/abicheck Action into one resolved
check — the single high-level primitive — and, once its own input validation
passes, always emits the report envelope
(§7), regardless of whether the baseline resolved, was a bootstrap "no
baseline yet" pass, a "target new to this baseline-set" pass
(allow-new-target: true), or failed outright. An invalid invocation (e.g. a
missing required input, or an unsupported input combination) is rejected
up front, before any of that, and produces no report or outputs at all.
Status. This page documents the
actions/check-targetcomposite Action shipped in G30 P1.3. The reusable workflows that generate arun-plan.jsonand fan this Action out over a matrix (check-single.yml/check-project.yml, G30 P1.4) are documented separately — see the run-plan schema and the reusable workflows reference.check-targetcan also still be called directly as a step (the S4 shortcut) or from a hand-written per-target workflow (S1/S2/S5/S6/S15/S21) without either.
What it does¶
- Resolves the baseline by composing
resolve-baseline— skipped entirely whenbaseline-channel: none(a single-build audit with no baseline). - Composes
collect-factswhenevidence-producerrequests build/source evidence:phase: verifyforwrapper/clang-plugin(the caller's own workflow must runcollect-facts phase: preparebefore its build step, earlier in the job —check-targetruns after that build already happened and cannot retroactively instrument it), orphase: autoforreplay(no pre-build hook needed). - Runs the analysis — the root Action's
comparemode against the resolved baseline, or (baseline-channel: none)compare's own audit-only shape (old-library/abi-baselineboth omitted, a one-build audit). - Writes the report envelope, once input validation (the very first
step) has passed — even when steps 1 or 3 above then fail, since the
internal resolve/analysis steps run with
continue-on-error: truespecifically so this step always runs afterward. A validation failure short-circuits before any of steps 1-4 run at all. - Owns its own composite exit code, per
gate-mode(below) — the very last thing this Action does, never an implicit pass-through of an internal step's raw exit code.
gate-mode¶
| Mode | This job's exit code | Who computes the real gate |
|---|---|---|
local (default) |
Reflects the real compatibility finding (today's root-Action behavior). | This job itself. |
deferred |
0 for a compatibility finding, whatever it is — but still nonzero on an operational error (a resolve-baseline failure, or the analysis step never producing a report). |
A trailing fan-in aggregate job, reading this check's real, un-neutralized severity/exit_code from its report. |
advisory |
Same as deferred. |
Nobody — findings are visible (in compatibility_verdict/policy_gate_decision) but never gate CI (shadow-rollout burn-in, S26). |
deferred vs. advisory reports differ in one important way: a
deferred report's severity/exit_code block stays the real value —
that's exactly what a trailing aggregate job's exit_code() (a max()
over every report's real gate) needs to compute the actual gate centrally.
An advisory report's legacy severity.exit_code/blocking are
neutralized to 0/false so an advisory check can never accidentally
raise aggregate's computed exit code above what the required cells alone
would produce — the real finding stays fully visible in the new
compatibility_verdict/policy_gate_decision fields for humans/PR
comments/SARIF, just never in the field aggregate gates on.
Operational errors are never deferred or hidden, regardless of
gate-mode: a resolve-baseline failure (not_found/ambiguous/
wrong_profile/stale_schema/incompatible_evidence) or the analysis step
never producing a report always fails this job's own exit code, exactly
like resolve-baseline's own fail-loud contract requires.
Target kinds¶
target-kind selects which compare flags this check builds — only
meaningful when kind: target (never kind: bundle):
target-kind |
Compare shape | Extra inputs |
|---|---|---|
library (default) |
A plain compare. |
— |
app-consumer |
compare --used-by (S22, application compatibility). |
consumer-binary |
plugin-contract |
compare --required-symbol @FILE (S23, plugin/dlopen contract). |
contract-file — a .syms file, one required linker symbol per line, # comments allowed; not YAML |
The "library redirect": app-consumer/plugin-contract
targets have no binary/baseline of their own — they resolve through the
library they scope. Set baseline-target to that library's id so
resolve-baseline looks up the right baseline, while name stays the
contract target's own name (myapp-consumer, ioc-plugin-contract) for
this check's own check_id/target_id identity, and new-library points
at that library's own candidate binary (candidate lookup needs the same
redirect as the baseline lookup — an app-consumer/plugin-contract target
has no binary_pattern of its own to resolve either side from).
kind: bundle (S14)¶
A bundle-scoped check never resolves a single snapshot — resolve-baseline
returns the bundle's staged member binaries (binaries-dir), since
abicheck/bundle.py's cross-library graph reads real ELF binaries, not JSON
snapshots. check-target hands that directory to the root Action's
compare mode as old-library directly — a plain directory-operand
compare, which compare already fans out to a per-library comparison
(including cross-library bundle findings) automatically; no separate
"bundle compare" mode or CLI command is invoked. new-library is the
caller-provided directory of the candidate build's own member binaries.
Two different questions a bundle answers¶
Don't conflate these; they are separate axes with separate exit contributions, and either can hold without the other.
| Axis | Question | Owner |
|---|---|---|
| Inventory / scope completeness | Were the required selected members actually compared? | scope.on_incomplete |
| Analysis assurance | For the comparisons that did run, how trustworthy was the evidence? | assurance.require_complete |
Neither is the compatibility verdict. A release can be scope-complete
with partial assurance (every member compared, one missing its headers) or
scope-incomplete with complete assurance (one member never supplied, the
rest fully analysed).
The assurance fold is max over compared members: an incomplete member is
never hidden by complete siblings, and over a one-member package the fold
is the identity, so it gates exactly as comparing that one library alone
would. See
Multi-binary → Analysis assurance across a bundle.
Three bundle paths, three sets of restrictions¶
"Bundles support X" is not a statement this documentation can make once. Three different paths reach a multi-library comparison and they do not share capabilities:
| Path | What it is | Restrictions |
|---|---|---|
| Direct CLI | abicheck compare OLD_DIR NEW_DIR (a directory/package operand, fanned out per library) |
The CLI's own; not bound by this Action's input validation |
| Stored member / bundle-facts | a stored ProjectSnapshot package or a BundleFacts document as an operand |
Limited by what the capture recorded |
Managed kind: bundle |
this Action, driven by check-project |
The table below |
A managed kind: bundle check is validated up front, before any setup work.
These combinations are rejected, and each rejection reflects an evidence
path that does not exist — not a documentation choice:
| Rejected | Why |
|---|---|
requested-depth: headers\|build\|source |
A bundle's baseline is staged raw binaries with no historical per-member header/build/source evidence. Only binary is supported. Use kind: target to compare one library at a deeper rung. |
baseline-channel: none |
There is no no-baseline bundle audit path: a bundle always compares directories, which compare's audit-only shape cannot do. |
allow-new-target: true |
A bundle comparison needs one coherent release where every member already coexisted. Scope a new member with its own kind: target check. |
analysis-assurance-complete is not in that list — see its row in
Inputs. Supporting a release-level assurance gate is a different
capability from supporting historical header-aware bundle capture; the first
landed, the second has not.
Inputs¶
| Input | Required | Default | Meaning |
|---|---|---|---|
kind |
no | target |
target or bundle. |
target-kind |
no | library |
library | app-consumer | plugin-contract (kind: target only). |
name |
yes | — | This check's own identity (check_id/target_id) — the target or bundle id. |
baseline-target |
no | (= name) |
Which target's baseline actually resolves — set to the referenced library for app-consumer/plugin-contract. Ignored for kind: bundle. |
bundle-members |
when kind: bundle |
[] |
JSON array of the bundle's member target ids. |
profile |
yes | — | The build profile.id this check runs under. |
baseline-channel |
yes | — | A channel name, or the literal none for a no-baseline audit (S5) — none is rejected for kind: bundle (a bundle has no no-baseline audit path; it always compares directories, which compare's audit-only shape cannot do). |
baseline-path |
when channel ≠ none |
'' |
Forwarded to resolve-baseline. |
baseline-required |
no | true |
Forwarded to resolve-baseline's required. |
candidate-build-output |
no | '' |
Forwarded to resolve-baseline's incompatible_evidence check. |
allow-new-target |
no | false |
Forwarded to resolve-baseline's allow-new-target — false means a target absent from an otherwise-resolved baseline-set always fails ambiguous; true opts this check into the new_target outcome instead, an advisory, non-fatal lifecycle state for a target checked before it has ever been published in a baseline-set (e.g. a new library's first release). Only meaningful for kind: target; rejected outright for kind: bundle (a bundle comparison needs one coherent release where every member already coexisted). Pair with baseline-required: false, or a required-coverage gate would still block on the target's first appearance. |
requested-depth |
yes | — | binary | headers | build | source — for kind: bundle, only binary is supported (headers/build/source are all rejected: a bundle's baseline is always raw binaries with no historical header/build/source evidence staged per member). |
explicit-id |
no | '' |
(G42 "Explicit check identifiers") This check's checks[].id, if the project declared one — folded into check-id's ~<explicit_id> tail so two checks sharing (name, profile, baseline-channel, requested-depth) but declaring different analysis method/policy/assurance each produce a distinct, non-colliding check-id/report. Omit (default) for the pre-G42, unqualified check-id shape. |
build-system / build-generator |
no | '' |
(WS-A) This cell's build-output.json profile.build_system name and generator. When build-system is set the report envelope carries profile_build_system: {name, generator} (report schema 5.11) in every mode, so the cell's findings name the build lane that produced them. check-project.yml forwards them from the run-plan cell. |
gate-mode |
no | local |
local | deferred | advisory. |
project |
no | ${{ github.repository }} |
Recorded in the report envelope. |
head-sha |
no | ${{ github.sha }} |
Recorded in the report envelope. |
base-ref |
no | '' |
Recorded in the report envelope. |
evidence-producer |
no | '' |
'' (no source evidence needed) | replay | wrapper | clang-plugin. |
evidence-pack-path |
no | abicheck_inputs |
Must match an earlier collect-facts phase: prepare step's own output path (wrapper/clang-plugin only). |
new-library |
yes | — | Candidate binary (kind: target) or directory of candidate member binaries (kind: bundle). |
consumer-binary |
when target-kind: app-consumer |
— | Forwarded as --used-by. |
contract-file |
when target-kind: plugin-contract |
— | Forwarded as the root Action's required-symbols input (translated to the CLI's --required-symbol @FILE). |
require-complete-analysis |
no | false |
RETIRED (rulings.py deferred-option followup — hard removal, no deprecation window, mirroring the root Action's own require-complete-analysis retirement, which this input forwarded to). The root Action's input it mapped onto is gone: P0.4's orthogonal ANALYSIS_INCOMPLETE axis is config-only now, .abicheck.yml's assurance.require_complete: true, with no CLI or Action-input override. Still declared so a workflow that sets it gets an explicit ::error:: instead of a silently-ignored input. See analysis-assurance-complete below for how checks[].analysis.assurance: complete (product-gaps audit §3) is now enforced instead. |
analysis-assurance-complete |
no | false |
Set to 'true' when this cell declared checks[].analysis.assurance: complete (RunPlanCheck.analysis_assurance, validated at run-plan generation time by project_targets.py's analysis_assurance_gate.py, which still accepts only 'complete'). This is the config-overlay replacement require-complete-analysis names as its successor: since neither a CLI flag nor an Action input can carry assurance.require_complete any more, this Action merges an assurance: {require_complete: true} fragment into whichever build-config the internal analysis step would otherwise read — the same config-only mechanism a project author's own .abicheck.yml line would produce. check-project.yml is the intended caller; a direct check-target caller may also set it explicitly. Supported for kind: bundle too: the release fan-out a bundle check runs folds every compared member's own analysis_assurance with max into the same exit axis a single-library check uses, so a one-member bundle gates identically to a kind: target check and any member that fell short floors the whole bundle. It used to be rejected outright for a bundle; that guard is retired. |
header, old-header, new-header, include, old-include, new-include, lang, ast-frontend, gcc-path, gcc-prefix, gcc-options, sysroot, sources, build-info, compile-db, build-config, policy, policy-file, suppress, severity-preset, severity-addition, extra-args, python-version, install-deps, dependency-source |
no | (mirror the root Action) | Forwarded straight through to the internal analysis step. dependency-source (G34 Phase C) is what check-project.yml sets per cell from the profile's own dependency_source:; the root Action owns its accepted-value list and its fallback to install-deps. |
Outputs¶
| Output | Meaning |
|---|---|
outcome |
The resolve-baseline outcome, or skipped when baseline-channel: none. |
check-id |
target@profile#baseline_channel@requested_depth — always includes the depth suffix, even in the common single-depth case. Gains a ~<explicit_id> tail when explicit-id is set (G42), e.g. target@profile#baseline_channel@requested_depth~my-id. |
verdict |
The legacy verdict field: one of the five Verdict values, ERROR (operational failure), NO_BASELINE (bootstrap pass), or NEW_TARGET (allow-new-target: true pass) — the latter two are deliberately not Verdict members, never a compatibility verdict. |
compatibility-verdict |
Mirrors verdict's casing, empty when unavailable (an operational-failure or bootstrap report). |
policy-gate-decision |
pass or fail — this check's own real gate decision, computed before any gate-mode: advisory neutralization. |
report-path |
Path to the final, enriched report JSON. |
Report envelope¶
Every run writes a single JSON report at
check-target-report-<name>-<profile>-<baseline_channel>-<requested_depth>-<digest>.json
(the exact path is always available via the report-path output — don't
hard-code the filename, since running check-target more than once in the
same job, e.g. the same target against two baseline channels, would
otherwise overwrite an earlier run's report; the trailing <digest> is a
12-hex-char SHA-256 prefix of the original, unsanitized identity tuple, so
two identities that collapse to the same slug under the filename's lossy
character substitution still produce distinct files — needed because
check-project.yml downloads every matrix cell's report into one shared
flat directory), starting from whatever the
underlying compare run already produced and layering on the
fields below. For a normal single-library compare (the common case),
that starting shape is abicheck/reporter.py's existing compare-report
shape (the one carrying report_schema_version — see
Output formats for that contract and
abicheck.schemas.current("compare") for the version this build emits).
A baseline-channel: none audit
instead starts from compare's own audit-only (--no-baseline) report
(its own audit_report_schema_version shape),
and a kind: bundle check starts from the CLI's per-library release
fan-out summary (libraries/old_dir, no schema-version marker of its
own) — neither of those two carries report_schema_version.
check_id/target_id— always the same, fully-qualified, depth-suffixed value, soabicheck aggregate's exact-match lookup lines up for every check, not only ones sharing a target with another check.profile_id,baseline_channel,requested_depth,effective_depth(may be shallower than requested when the evidence wasn't actually available —check_evidence_coveragerecords why).compatibility_verdict/policy_gate_decision— the new, richer fields — alongside, never instead of, the legacyverdict/severityfieldsabicheck/workflows/aggregate/already parses (the dual-write requirement).operational_errors— non-empty exactly when this check hit an infrastructure/config problem rather than (or in addition to) a compatibility finding.publication— whether/where this report was actually published.
A resolve-baseline failure, a bootstrap ("no baseline published yet")
pass, or a new-target ("baseline-set resolved but has no entry for this
target yet", allow-new-target: true) pass all synthesize this same
envelope from scratch — a report always exists, even when no comparison
ever ran.
Example¶
- name: Check libpvxs against accepted-main
uses: abicheck/abicheck/actions/check-target@c9e135a3233b6d45e9571533f71293fde458a469 # not yet in a tagged release; pin main or newer
with:
name: libpvxs
profile: linux-x86_64-gcc13-release
baseline-channel: accepted-main
baseline-path: ./restored-baseline # staged by an earlier actions/cache step
requested-depth: headers
gate-mode: local
new-library: build/lib/libpvxs.so
header: headers/pvxs/*.h
# S5: single-build audit, no baseline.
- name: Audit libpvxs (no baseline)
uses: abicheck/abicheck/actions/check-target@c9e135a3233b6d45e9571533f71293fde458a469 # not yet in a tagged release; pin main or newer
with:
name: libpvxs
profile: linux-x86_64-gcc13-release
baseline-channel: none
requested-depth: headers
gate-mode: advisory
new-library: build/lib/libpvxs.so
header: headers/pvxs/*.h