Skip to content

feat(reporting): record the evaluated source identity on benchmark cards - #131

Merged
rng1995 merged 15 commits into
NVIDIA:mainfrom
MaxFreedomPollard:fix/issue-72-evaluated-source-provenance
Sep 17, 2026
Merged

rng1995 merged 15 commits into
NVIDIA:mainfrom
MaxFreedomPollard:fix/issue-72-evaluated-source-provenance

Conversation

@MaxFreedomPollard

@MaxFreedomPollard MaxFreedomPollard commented Sep 4, 2026 •

Copy link
Copy Markdown
Contributor

What this changes

A published BENCHMARK.md records the evaluator build but not the source tree it evaluated. As #72 shows, three skills from three different repositories published cards whose only recorded revision was 0117bc2e…, the SkillEvaluator container tag they happened to share. A reviewer reading a committed card cannot tell which version of a skill was evaluated, or whether the card went stale after the source changed.

This carries an evaluated-source identity through the result contract and renders it with labels that are unambiguously distinct from the evaluator provenance:

- Evaluator version: `0.8.2`
- Evaluated source: `NVIDIA/NVFlare`
- Evaluated source revision: `2263a2ebdab903e87f7e7c0a001d22c3a926a9cf`
- Evaluator container revision: `ghcr.io/nvidia/skillevaluator@sha256:0117bc2e…`

Design notes

The identity comes from the orchestration input, never from repository state. Per the issue's requirement, nothing is inferred at render time: the tree that renders a card is the evaluator checkout, not the evaluated source, so inferring it there would reproduce the bug being fixed. Four seams supply it:

  • --evaluated-source-repository, --evaluated-source-revision and --evaluator-container-revision on validate and on tier3 evaluate. validate records the identity on the card and in its JSON report and forwards it to every child of a parallel catalog run; both commands persist it into a Tier 3 run's run_config.json. A value that is not canonical is refused at the CLI boundary rather than dropped;
  • the evaluated_source argument to build_agent_eval_payload / agent_eval_result_from_directory;
  • an evaluated_source object in the run's run_config.json;
  • metadata["evaluated_source"] on any validation result.

That last seam matters more than it looks: a card can publish a PASS without a completed Tier 3 run (Tier 1-only, or an advisory Tier 3 skip under tier3_required = false). Those shapes have no agent_eval payload to carry an identity, so without a result-level carrier the strict gate below would be permanently unsatisfiable for any catalog containing one. The renderer resolves the identity the same way _benchmark_policy already resolves the persisted policy.

Conflicting inputs fail rather than resolving by precedence. Acceptance criterion 3 asks publication to fail closed when provenance "is missing or conflicts with the orchestration input". Every carrier the run recorded (the CLI input, every result's metadata, and the top level and summary of every Tier 3 payload) is folded into one identity field by field, so a typo in one cannot silently discard a good identity in another, and a partial identity is completed rather than left to shadow the rest. A field two carriers supply with different values raises EvaluatedSourceConflict. validate resolves the identity before any report is emitted, so a contradictory run writes nothing at all instead of a JSON report that disagrees with its card, and result ordering can never decide which source tree a card names.

One definition, shared by producer, renderer and gate. skillevaluator/source_identity.py is a small dependency-free module holding the field rules. It sits alongside evidence.py and spdx.py rather than under evaluation/ or reporting/, because evaluation already imports reporting and the renderer needs it too. (The CI script mirrors the shapes as literals rather than importing, since it is deliberately stdlib-only; a test asserts the mirrored pattern text is identical.)

Revisions are accepted only in canonical shapes. A source revision is a full Git object id (40 or 64 hex characters) or a sha256/sha384/sha512 digest at its algorithm's width; a short prefix can grow a second match as a tree grows, so it is refused. A container revision is an OCI image reference validated component by component: an optional registry host (localhost, a dotted name, or a name with a port), a lowercase repository path of at most 255 characters, an optional tag of at most 128, and a digest at its algorithm's width. A bare image name is refused because it names no revision, and a published PASS must pin the evaluator by digest or by a full implementation revision, because a tag can be repointed after the card is published.

Validated values are published verbatim, and that is deliberate. An earlier draft ran the identity through _publication_safe_inline. That escaping rewrites _ to \_ and @ to @, which silently corrupted holgerroth/nvflare_examples and ghcr.io/…@sha256:…, turning the immutable, digest-pinned reference the issue asks for into a string resolving to nothing, which this patch's own gate then rejected. Validation is what makes a value safe, so the card emits the canonical value unchanged. Every accepted character is inert inside a code span, and the renderer re-validates rather than trusting the payload, so a card rendered from a hand-built or legacy metadata dict drops a hostile value instead of escaping it into the output. Digest algorithms are an allowlist (sha256/sha384/sha512), not a charset, so totally-fake:0000… cannot read as a canonical digest.

revision, not commit. The issue accepts "commit SHA, or a canonical digest of the evaluated skill content". A line labelled commit carrying a sha256: digest would be its own small lie, so the line reads Evaluated source revision; a commit takes precedence when both are supplied.

An absent identity says why. These fields fall back to not recorded (not supplied by the orchestration input) rather than borrowing the existing legacy or non-live result wording, which would be false: a fresh live run with no orchestration input is neither legacy nor non-live.

The gate check is opt-in, and this repository's CI enables it. --require-source-provenance requires the three fields and fails any card publishing a PASS without a recorded repository, revision and digest-pinned container, whatever the Tier 3 row says, and whether or not the verdict line is blockquoted (the rollout runbook points this flag at hand-authored backfill trees). Only a verdict line counts as a published PASS; the policy bullet that restates the rule on every card does not. Without the flag the scan is byte-compatible with its previous behaviour: cards generated before this contract existed cannot carry these fields. tests/test_public_benchmark_gate.py is untouched by this diff, which is the evidence that default behaviour did not move.

If you would rather it fail closed by default, say the word and I will flip it and update the fixtures here.

Scope

Two of the issue's acceptance criteria are deliberately not addressed:

  • "Signed output covers the recorded source identity": there is no card-signing path in this repository (output_provenance.py covers atomic writes, not content signatures), so this belongs downstream.
  • "Existing cards using Git commit are renamed or regenerated": the renderer on main already stopped emitting the ambiguous Git commit field, as the issue notes. Regenerating already-published downstream cards is outside this repo.

I also left _tier3_evidence_complete() alone: making the "Recommended for publication" decision depend on source provenance is a policy change with far wider blast radius than the CI gate.

The unreachable argv-scraping catalog child builder (_catalog_child_argv_from_sys and its dispatcher) is removed: it discarded every option value, so reinstating it would have silently dropped the provenance options this branch forwards.

Tests

tests/test_benchmark_source_provenance.py holds 157 tests covering normalization (including the container reference grammar on both the library and the gate), cross-channel resolution and conflict detection in both result orders, the payload contract, rendering, the non-Tier-3 carrier, both gate modes, the main()/find_offenders() entry points, a reporter→gate round-trip parametrized over the values that previously broke (_ in a repository name, an @sha256: container reference, a content digest), the CLI supplying the identity, the JSON report carrying it, a conflict aborting before any report file exists, and a standalone run persisting it into run_config.json. tests/test_commands.py and tests/test_cli.py cover the parallel catalog children, a drift guard that fails when a validate option is neither forwarded to children nor explicitly excluded, and the tier3 evaluate options.

It includes the regression the issue names, "two source repositories evaluated with the same evaluator image retain different source SHAs", and asserts the stronger property that actually matters: neither card's source revision ever equals the shared container revision, which is the exact confusion #72 reported. A companion test covers a run with no commit, checking the card says not recorded rather than borrowing the evaluator's revision.

Hostile-payload tests confirm that markdown, HTML and line-injection attempts through an unvalidated metadata dict are dropped rather than escaped into the card.

Verification

  • uv run python -m pytest -q on Python 3.12 and on 3.13: 6227 passed, 17 skipped
  • uv run ruff check .: all checks passed
  • scripts/ci/check_public_benchmarks.py --require-files tests/golden --require-source-provenance (the exact CI invocation): passes
  • The same gate without the flag: passes, unchanged
  • A pre-patch card scanned with default flags: passes, unchanged
  • Golden benchmark_pass / benchmark_fail regenerated; the fixture deliberately uses NVIDIA/demo_skills and a digest-pinned container reference, so the two previously-corrupted characters are covered by the golden itself

One note in passing, unrelated to this change: make lint runs ruff check src tests and reports 54 pre-existing errors on a clean main, while CI runs ruff check . and is green. The Makefile target may want to match CI.

Closes #72

@chrisknvidia

Copy link
Copy Markdown
Collaborator

@mosheabr : What do you think about this Benchmark changes?

Comment thread scripts/ci/check_public_benchmarks.py Resolved
Comment thread src/skillevaluator/source_identity.py Resolved

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The source-identity plumbing is useful and the focused suite (166 tests), Ruff, and remote CI pass, but four provenance guarantees are still missing: normal validation does not supply or enforce the identity, contradictory carriers silently pass, revision syntax accepts ambiguous or algorithm-invalid values, and strict mode omits the evaluator container revision. These make a published PASS appear source-bound when it is not. Please address the inline findings; the current CHANGELOG.md conflict with main also needs resolution.

Comment thread src/skillevaluator/source_identity.py Resolved
Comment thread src/skillevaluator/cli.py Resolved
A published BENCHMARK.md recorded the evaluator build but not the source
tree it evaluated. Two skills evaluated from different repositories by the
same evaluator container therefore produced cards whose only revision was
the shared container tag, so a reviewer could not tell which version of a
skill was evaluated or whether a card had gone stale.

Carry an evaluated-source identity through the result contract and render
it with labels unambiguously distinct from the evaluator provenance:

- Evaluated source: `NVIDIA/NVFlare`
- Evaluated source revision: `2263a2eb...`
- Evaluator container revision: `ghcr.io/nvidia/skillevaluator@sha256:...`

The identity comes from the orchestration input and is never inferred from
repository state while rendering: the tree that renders a card is the
evaluator checkout, not the evaluated source. It can arrive as the
`evaluated_source` argument, as `evaluated_source` in `run_config.json`, or
as `metadata["evaluated_source"]` on any validation result -- the last so a
card that publishes without a completed Tier 3 run still has a carrier.
Where two channels supply the same field and disagree, resolution fails
rather than silently picking one, because a card that quietly chose between
two contradictory revisions is the unverifiable provenance this contract
exists to prevent.

`skillevaluator.source_identity` holds one definition of the identity so
producer, renderer and gate agree. Values are accepted only in canonical
shape and are then published verbatim rather than Markdown-escaped, because
escaping rewrites `_` to `\_` and `@` to `@` and would publish a
repository name and a digest-pinned image reference that no longer resolve.
Every accepted character is inert inside a code span, and the renderer
re-validates rather than trusting the payload, so a card built from a
hand-made or legacy metadata dict drops a hostile value instead of escaping
it into the output.

The revision line is labelled `revision` rather than `commit` because the
issue accepts either a commit SHA or a canonical content digest, and an
absent identity says it was not supplied by the orchestration input rather
than blaming a legacy or non-live result.

`check_public_benchmarks.py --require-source-provenance` requires the three
fields and fails any card publishing a PASS without a recorded repository
and revision, whether or not that verdict is blockquoted and whatever the
Tier 3 row says. The whole check is opt-in: cards generated before this
contract existed cannot satisfy it, so the default scan stays byte
compatible with its previous behaviour and CI can enable the flag once the
orchestration side supplies the identity.

Closes #72

Signed-off-by: Max Freedom Pollard <[email protected]>
…ntities

Addresses three review findings on the evaluated-source contract.

Contradictory carriers no longer resolve by position. The renderer read the
Tier 3 payload, that payload's summary and every result's metadata as an
ordered candidate list and published the first that validated, so a card whose
Tier 3 payload named one repository and whose Tier 1 result named another
published whichever the iteration order reached first. Result ordering follows
tier execution rather than orchestration input, so it must not decide what a
card claims to describe. All populated carriers are now folded together and a
field two carriers disagree about raises EvaluatedSourceConflict. The existing
two-channel resolver is the two-item case of that fold, so its per-field merge
and its error wording are unchanged. Because the card is generated compulsorily
for skills, `validate` reports that conflict as a CLI error naming the values
that disagreed, rather than an unhandled traceback.

Revisions are accepted only in canonical shapes. The commit pattern admitted a
7-character prefix, which can grow a second match as a tree grows, and the
digest pattern admitted any algorithm followed by 32 to 128 hex characters, so
sha512 with 32 hex characters rendered as immutable provenance. A commit must
now be a full Git object id (40 or 64 hex) and a digest must match the width of
the algorithm it names. The stdlib-only publication gate mirrors both rules.

Strict publication mode now requires the evaluator container revision, and
requires it to be pinned by digest. Blanking the golden card's container digest
previously produced no offenders, which contradicted both the flag's
documentation and the confusion issue #72 reported, where the only recorded
revision was a shared evaluator image.

Tests cover each rule, including the reviewer's reproduction: a Tier 3 payload
and a Tier 1 result naming different repositories now fails closed in either
result order, rather than publishing one of them.

Signed-off-by: Max Freedom Pollard <[email protected]>
…n input

Nothing in the tree populated the identity, so a normal validate run rendered
all three provenance fields as not recorded and the confusion issue #72
reported stayed reproducible.

`validate` now accepts --evaluated-source-repository,
--evaluated-source-revision and --evaluator-container-revision. One revision
option covers both immutable shapes the card accepts, matching its single
"Evaluated source revision" line: a full Git object id, or a content digest for
a run with no upstream commit. A value that is not canonical is refused at the
CLI boundary rather than dropped, so a card never reports not recorded for a
field the operator believed they had supplied.

The identity reaches the card through the result metadata carrier, which is the
seam that works for every card shape including a Tier 1-only PASS. It is
attached with setdefault, so an identity a producer already recorded stays in
place and a disagreement between the two is reported rather than resolved.
For a Tier 3 run it also travels EvaluationOptions to tier3.commands.evaluate
to the Harbor runner, which persists it in run_config.json, so a card rendered
later from that run directory records the same source tree.

CI now runs the publication scan with --require-source-provenance. The flag
remains opt-in for trees whose cards predate the contract.

Without these options the card renders not recorded exactly as before, which
the default-path test asserts.

Signed-off-by: Max Freedom Pollard <[email protected]>
@MaxFreedomPollard
MaxFreedomPollard force-pushed the fix/issue-72-evaluated-source-provenance branch from 6bb9b2c to c568326 Compare September 16, 2026 04:10
MaxFreedomPollard and others added 10 commits September 16, 2026 00:46
The evaluator container revision was capped at 128 characters over the whole
reference, which charges the `@sha256:` suffix to the repository name. A
68-character repository pinned by digest is 140 characters, so it was silently
dropped and the card reported the revision as not recorded even though the
orchestration input had supplied it.

The field is validated against the OCI reference grammar instead, component by
component: a name of at most 255 characters (NameTotalLengthMax) built from an
optional registry domain with an optional port and slash-separated path
components, an optional tag of at most 128 characters, and an optional digest
at its own algorithm's width, from the same allowlist the content digest uses.
A reference carrying neither a tag nor a digest is refused, because a bare name
identifies a repository rather than the build that ran. An evaluator run from a
source checkout still records its own implementation revision, and a published
PASS still requires the digest, because a tag can be repointed afterwards.

Every accepted character remains inert inside a Markdown code span, so the card
can keep rendering the value verbatim rather than escaping an identity into
something that no longer names the thing it identifies.

The stdlib-only publication gate mirrors the library rather than importing it,
so it carries the same pattern text and the same length rule as literals, and a
new test compares the two strings so the copies cannot drift unnoticed. Both
sides now hold a predicate per field, which is what lets one rule combine a
pattern with a length check the pattern cannot express.

Signed-off-by: Max Freedom Pollard <[email protected]>
One ordering-dependent path survived the previous fix. The card resolved the
evaluated source from the first result carrying an `agent_eval` payload, so a
run that produced two agent-eval results published whichever payload came
first: reversing the result list flipped the card to the other source tree
instead of raising EvaluatedSourceConflict. Result order follows how a run was
scheduled, so it must not decide what a published card claims to describe.

`source_identity` now owns the whole set of carriers. `evaluated_source_carriers`
names the three places one result can record the identity: its own metadata, the
agent-eval payload, and that payload's summary. `recorded_evaluated_source`
folds every carrier of every result through the existing per-field merge, so a
second payload can no longer be shadowed and a disagreement anywhere fails
closed in either order. Keeping the set in one place also keeps the card and the
machine-readable report reading the same carriers, rather than honouring one in
a report and ignoring it in another.

The renderer still selects the first payload for the score and run-detail lines,
which describe a single run, and no longer uses that selection to resolve
provenance.

The regression covers the reviewer's reproduction in both orders: two full
payloads naming different repositories, and a first payload against a
summary-only carrier on a second result. Two payloads that agree still render
the identity rather than reading as a conflict.

Signed-off-by: Max Freedom Pollard <[email protected]>
`validate` attached and resolved the identity after `emit_reports`, so the
machine-readable report disagreed with the card published beside it. A
Tier 1-only run wrote a JSON report carrying neither the source repository nor
its revision while BENCHMARK.md recorded both, and a run whose carriers
contradicted each other wrote JSON and HTML naming one of the two before
benchmark generation failed the run.

The attach and resolve step now runs immediately after `apply_policy`, for
every content type rather than only for skills, so a run that cannot say what
it evaluated writes nothing at all. The supplied identity is folded in as its
own carrier rather than only attached: `setdefault` is silent where it declines
to overwrite, so a result that already recorded a different source tree would
otherwise publish that one and drop the operator's orchestration input
unreported. The BENCHMARK.md block keeps its own conflict handling as a last
line of defence, because the renderer re-validates whatever carriers it is
handed.

The JSON report now always carries a top-level `evaluated_source` resolved from
the same fold, so CI can verify provenance from the file it already parses. The
key is always present: `null` says the orchestration input supplied none,
rather than leaving a consumer to guess whether an older reporter omitted it.

Tests cover a Tier 1-only run whose JSON report and card record the same
identity, the JSON contract for a recorded, absent and contradictory identity,
and a payload/CLI conflict that leaves no JSON, HTML or BENCHMARK.md behind.

Signed-off-by: Max Freedom Pollard <[email protected]>
…llel catalog children

The single-skill `validate` path supplied the identity, but the two other
producers still dropped it, so the contract held only for the narrowest run
shape.

`tier3 evaluate` exposed no provenance options at all. It owns its own run
directory, so a standalone Tier 3 run persisted `evaluated_source: null` into
`run_config.json` and any card rendered later from that directory reported the
source tree as not recorded, which is the confusion the contract exists to
prevent. The command now accepts the same three options as `validate`, with the
same names and the same canonical-shape refusal at the CLI boundary, and passes
the resolved identity through `EvaluationOptions` to the engine. The visible
`tier3 evaluate` re-lists the hidden command's params, so both spellings pick
the options up.

`validate <catalog> --workers > 1` rebuilds each child's argv from an explicit
allowlist, and that allowlist omitted all three options. Every child renders its
own BENCHMARK.md, so a parallel catalog run published a whole directory of cards
saying the evaluated source was never recorded while the serial run of the same
catalog recorded it. The three options are now forwarded with the rest.

A drift guard covers the allowlist itself: every `validate` parameter must
either be forwarded by name or appear in the test's not-forwarded map with the
reason it must stay behind, so the next option added to `validate` cannot be
silently dropped on the way to a child.

The standalone path is covered down to disk rather than by signature: a run
driven through `tier3.commands.evaluate` with a failing runtime preflight still
writes `run_config.json`, and the test asserts the identity is in it. The
parallel path is covered end to end on a two-skill catalog, asserting both
children's cards name the repository and the supplied revision.

Signed-off-by: Max Freedom Pollard <[email protected]>
The entry now covers the producers that carry the identity (both commands,
parallel catalog children and the JSON report), that conflicting carriers
fail before any report is written, and the component rules a container
revision is validated against.

Signed-off-by: Max Freedom Pollard <[email protected]>
`_finalize_evaluated_source` attached the supplied identity with `setdefault`
and then folded the carriers only to detect a conflict, discarding the merged
value. A result that had already recorded part of the identity therefore
shadowed the rest of it. A Tier 1 result carrying just
`metadata["evaluated_source"] = {"repository": "NVIDIA/NVFlare"}`, which is the
documented seam for a card published without a completed Tier 3 run, published
a card and a JSON report calling the revision and the evaluator container not
recorded, though the operator had supplied both on the command line. Nothing
said so: the fold saw no disagreement, because a partial carrier does not
conflict with the fields it omits.

The fold's result is now what gets written. Every carrier, the orchestration
input included, is merged into one identity, and that union is written back to
each result whose metadata is a mapping, as a fresh dict per result so no two
results and no caller share one mutable identity. A run that recorded no
identity anywhere still has none written, so absent stays absent rather than
becoming empty. Conflicts keep failing closed with nothing on disk.

The hidden `evaluate` command advertised its repository option as "recorded on
BENCHMARK.md", which that command never writes. It says where the value
actually lands instead: the run's `run_config.json`, which a card rendered
later reads.

Signed-off-by: Max Freedom Pollard <[email protected]>
The reference grammar read any single first label as a registry host, so
`NVIDIA/skillevaluator@sha256:...` was accepted while the same repository under
a registry, `ghcr.io/NVIDIA/skillevaluator@sha256:...`, was refused. The OCI
grammar has no such asymmetry. A first component is a host only when it is
`localhost`, when it carries a dot, or when it carries a port; anything else
begins the path, whose components are lower case. Both spellings above are an
uppercase path component, so both are refused now, and a host is recognised in
the three shapes the grammar names, in any case, because DNS is
case-insensitive.

The 255-character bound was applied to the host and the path together. The
grammar's RepositoryNameTotalLengthMax measures the path once the host has been
split off it, so a long registry name was spending a budget that belongs to the
path. The path is now its own capture group and the bound measures that group,
in the library and in the gate alike. The constant says path rather than name,
so the two cannot be read as the same thing again.

The gate's `_GIT_OBJECT_ID` was a hand copy of `_SOURCE_COMMIT` that the drift
test never compared, so it could have drifted while the reference grammar still
matched. Its text is now identical, anchors included, and the drift test
compares it alongside the reference pattern and the bound.

Adversarial inputs were measured through both copies of the grammar before this
landed: 5,000-character runs of `-`, `.`, `_`, `/` and alternating `a-`
complete in about 1.2 ms, so the added alternation costs no catastrophic
backtracking.

The docs and the changelog now state the rule the code enforces, including the
lower-case path components, which were written down nowhere.

Signed-off-by: Max Freedom Pollard <[email protected]>
`_PUBLISHED_PASS` matched any line that contained "Overall verdict: PASS",
because its `.*` ran straight past the list marker that opened the line. Every
card carries the policy section, which restates the rule as a bullet, "- Overall
verdict: PASS only when every configured dimension passes for at least one
supported agent", so an INCOMPLETE or a FAIL card read as a published PASS.
Under `--require-source-provenance` that card was then asked for the three
provenance fields a publication claims, and the scan reported three offenders
against a card that published nothing. A local INCOMPLETE card failed the strict
scan on exactly those three lines, none of which it was answerable for.

Both detectors now read a verdict line and nothing else. A list item is excluded
at the line start, and the verdict has to close its line or introduce the
wording that explains it, so the rendered callout, `> **Overall verdict: PASS`
followed by the recommendation, still matches while the policy bullet does not.
The two are built from one shared tail, so they cannot drift into disagreeing
about what a verdict line is. `_PUBLISHED_PASS` still accepts a missing
blockquote marker, which is how a hand-authored backfill card states its
verdict, and `_OVERALL_PASS` still requires one.

The strict scan of a tree of cards is the only place this was visible, so the
tests scan the two shapes that matter: a non-PASS card carrying the policy
bullet, which must raise no publication offence, and the published card, which
carries the same bullet and must still be charged for provenance it omits.

Signed-off-by: Max Freedom Pollard <[email protected]>
`_catalog_child_argv` had no caller anywhere in the tree, and the only reason
`_catalog_child_argv_from_sys` existed was to be chosen by it. The parallel
catalog path calls `_catalog_child_argv_from_ctx` directly, so both functions
were a second answer to a question the code stopped asking.

Keeping them was not free. The sys builder rebuilt a child command by scraping
the parent argv, and it kept only the flags: anything that did not start with
`-` was skipped, option values included. An option that takes a value therefore
came out bare. Reinstated on this branch, `--evaluated-source-repository
NVIDIA/demo` would have reached the child as the option alone, and Click would
have read the appended `-o` as its value, leaving the output directory a stray
argument. The identity this branch forwards to every catalog child would have
gone missing on the way, and the cards would have said the evaluated source was
never recorded. Deleting the builder closes that path rather than leaving it in
reach of the next person who needs one.

Signed-off-by: Max Freedom Pollard <[email protected]>
@MaxFreedomPollard

Copy link
Copy Markdown
Contributor Author

@rng1995 All six findings from the 12 September and 16 September passes are addressed, and I have resolved the threads. The CHANGELOG conflict with main is cleared in 323ca56.

The three workflows on this PR are sitting at action_required rather than failing. This is a fork PR, so the runs need a maintainer to approve them, and every push re-arms that gate. @chrisknvidia @rmalani-nv could one of you approve the workflow run? Until then, here is the CI reproduced locally at 323ca56 on macOS, Python 3.12, uv 0.11.2:

CI step Result
check_oss_boundary.py --root . --allowlist config/oss_boundary_allowlist.json OSS boundary scan passed
check_public_benchmarks.py --require-files tests/golden --require-source-provenance Public benchmark scan passed (2 files)
ruff check . All checks passed
pytest -q --cov=skillevaluator 6236 passed, 17 skipped, 84% total coverage

The tests named in my replies all pass, 89 in total: TestNormalization, TestStrictGateRevisionSyntax, test_ambiguous_revision_does_not_satisfy_a_pass, test_the_gate_mirrors_the_library_reference_grammar, test_pass_without_a_container_revision_is_flagged, test_mutable_container_tag_does_not_satisfy_a_pass, TestEveryNestedAgentEvalCarrierIsFolded, test_a_standalone_run_persists_the_identity_in_run_config, test_catalog_child_argv_forwards_every_validate_option, test_validate_catalog_workers_records_the_evaluated_source_on_every_card, TestTier1OnlyReportsCarryTheIdentity, TestConflictAbortsBeforeAnyReportIsWritten, TestPartialRecordedIdentityMergesWithTheInput.

Two limits on that local run, so you can weigh it accordingly. The Linux, Windows and RHEL 8 jobs cannot run on this machine, so the figures above are the macOS subset plus the lint and gate steps. One test fails locally, test_strict_exec_bootstrap_runs_from_fresh_private_tmp_venv_under_real_seatbelt, because python -m venv cannot reach ensurepip under /private/tmp in my shell. It fails the same way on a clean origin/main at 73b27da, so it does not come from this branch.

DCO is satisfied: every non-merge commit on the branch carries a Signed-off-by trailer.

The CHANGES_REQUESTED from 12 September is still the only thing blocking. When you have a moment for a third pass, I would appreciate the re-review.

@rng1995 rng1995 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the latest head against all six inline findings. The strict container provenance rule, conflict folding across every carrier, canonical immutable revision validation, production/standalone/parallel identity plumbing, component-wise OCI reference validation, and pre-report finalization are all implemented and covered. Independent validation passed the 261 focused provenance/CLI/catalog tests, Ruff, the strict public-benchmark gate, and Fern docs checks. The broad suite produced 6,335 immediate passes; all 98 cases initially blocked by workspace socket/Seatbelt permissions or the host python3 path passed on controlled reruns with the required host permissions and project interpreter. The head merges cleanly with current main. GitHub fork workflows remain action_required pending separate maintainer authorization.

@rng1995
rng1995 enabled auto-merge (squash) September 17, 2026 21:01
@rng1995
rng1995 merged commit a8d1614 into NVIDIA:main Sep 17, 2026
17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[BUG]: Published BENCHMARK.md does not identify the evaluated source revision

3 participants