Skip to content

feat(evidence): per-source pointer contract + signer allowlist (#1401)#1485

Merged
njhensley merged 5 commits into
NVIDIA:mainfrom
njhensley:feat/per-source-evidence-pointers
Jun 26, 2026
Merged

feat(evidence): per-source pointer contract + signer allowlist (#1401)#1485
njhensley merged 5 commits into
NVIDIA:mainfrom
njhensley:feat/per-source-evidence-pointers

Conversation

@njhensley

Copy link
Copy Markdown
Member

Summary

Establishes the in-repo recipes/evidence/ per-source pointer contract (#1347, Option A): one immutable single-attestation pointer per run at recipes/evidence/<recipe>/<src>/<bundle-digest>.yaml, plus a maintained signer allowlist and CI path-ownership enforcement.

Motivation / Context

Evidence pointers were keyed solely by recipe slug, so a second contributor's submission for the same recipe silently overwrote the first. This adopts the #1347 directory-per-source layout (add-only, immutable), where <src> is a one-way slug derived from the verified signer OIDC identity — so two sources for one recipe never collide and the path is not squattable. It also stands up the maintained signer allowlist (the trust root for the corroboration dashboard, epic #1400) and the path-ownership CI gate.

Fixes: #1401
Related: #1347, #1400

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Build/CI/tooling
  • Documentation update

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • Docs/examples (docs/, examples/)
  • Other: pkg/evidence/*, recipes/evidence/, .github/ (CI + CODEOWNERS)

Implementation Notes

  • attestation.SourceSlug(issuer, identity) = first 12 hex of sha256(issuer + "\n" + identity). Deriving <src> from the signer (not a free-form label) is what makes the path non-squattable and keeps it aligned with the GP2 ingest keying.
  • Migrated the two existing flat pointers into the per-source layout (git mv, content unchanged — already single-attestation).
  • pkg/evidence/allowlist + recipes/evidence/allowlist.yaml — the trust root. Classes first-party / community / partner, validated disjoint and non-overlapping. Community/partner entries are slug-only (source + issuer + optional non-PII label) so no cleartext contributor identity is committed; the loader rejects a stray identity: field. First-party entries pin tightly-bounded workflow identityPatterns (CI URLs, not personal PII). A verified-but-unlisted signer is admitted as reported, never corroborating.
  • verifier.DiscoverPointers (glob <recipe>/*/*.yaml) for multi-source discovery, and verifier.CheckEvidenceTree for the anti-squat gate: a pointer must be signed, hash to the <src> directory it lives in, match its recipe directory, and be allowlisted community/partner.
  • CI: tools/evidence-pointercheck + the evidence-pointer-contract workflow run the gate offline (no OCI fetch — fork-safe). CODEOWNERS makes maintainer review the trust gate. The warning-only recipe-evidence-check.sh and the emit/publish copyTo hint were generalized to the per-source layout.
  • First-party UAT ingests evidence directly (no per-run commit, which would churn main); committed pointers are the community/partner channel.
  • Out of scope (per the issue): the GP2 ingest job, GP3 bucket/SA, and GP4 generator.

Testing

go test ./pkg/evidence/... ./pkg/cli/... ./tools/evidence-pointercheck/...
golangci-lint run -c .golangci.yaml ./pkg/evidence/... ./tools/evidence-pointercheck/...
./tools/check-docs-mdx && ./tools/check-docs-filenames
yamllint -c .yamllint.yaml recipes/evidence/
go run ./tools/evidence-pointercheck   # OK against the committed tree

All green. New-code coverage: allowlist 92.9%, SourceSlug 100%, discover.go 84–100%. (Race detector requires cgo/gcc, unavailable in the dev sandbox; CI runs -race.)

Tests cover the required cases: pointer parse/validate, two-sources-one-recipe glob discovery (no overwrite), path-ownership/squat rejection, allowlist schema (slug format, disjoint, no over-broad pattern, rejects cleartext identity), and deterministic pointer YAML.

Risk Assessment

  • Low — Additive: new packages, a new CI gate, and a layout migration of two existing pointer files. The pointer file schema is unchanged; consumers gain discovery + a path convention.

Rollout notes: First-party direct-ingest is wired by GP2 (separate); until then UAT continues to upload its pointer as a CI artifact (it does not commit per-run). No migration required for existing consumers.

Checklist

  • Tests pass locally (make test with -race) — non-race locally (no cgo in sandbox); CI runs -race
  • Linter passes (make lint)
  • I did not skip/disable tests to make CI green
  • I added/updated tests for new functionality
  • I updated docs if user-facing behavior changed
  • Changes follow existing patterns in the codebase
  • Commits are cryptographically signed (git commit -S)

@njhensley njhensley requested review from a team as code owners June 26, 2026 08:12
@njhensley njhensley added the theme/supply-chain SLSA, SBOM, Sigstore, and provenance verification label Jun 26, 2026
@njhensley njhensley marked this pull request as draft June 26, 2026 08:16
…A#1401)

Establish the in-repo recipes/evidence/ pointer contract per NVIDIA#1347
(Option A): one immutable single-attestation pointer per run at
recipes/evidence/<recipe>/<src>/<bundle-digest>.yaml, where <src> is a
one-way slug derived from the verified signer's OIDC identity. Two
sources for one recipe no longer collide, and the path is not
squattable.

- Add attestation.SourceSlug(issuer, identity) =
sha256(issuer\nidentity)
  first 12 hex; aligns with GP2 ingest keying.
- Migrate the two flat pointers into the per-source layout.
- Add pkg/evidence/allowlist + recipes/evidence/allowlist.yaml as the
  maintained trust root: first-party / community / partner classes,
  disjoint and non-overlapping. Community/partner entries are slug-only
  (source + issuer + optional non-PII label) so no cleartext contributor
  identity is committed; the loader rejects a stray identity field.
  First-party entries pin tightly-bounded workflow identityPatterns.
- Add verifier.DiscoverPointers (glob multi-source discovery) and
  verifier.CheckEvidenceTree (anti-squat: signed, signer hashes to its
  own dir, allowlisted community/partner).
- Enforce in CI: tools/evidence-pointercheck + evidence-pointer-contract
  workflow (offline, fork-safe); CODEOWNERS makes maintainer review the
  trust gate.
- Generalize the warning-only recipe-evidence-check.sh and the
emit/publish
  copyTo hint to the per-source layout; update CLI help and docs
  (artifact-verification, validation, maintaining, recipe-development,
  cli-reference, ADR-007).

First-party UAT ingests evidence directly (no per-run commit); committed
pointers are the community/partner channel. Out of scope: GP2 ingest,
GP3 bucket/SA, GP4 generator.
@njhensley njhensley force-pushed the feat/per-source-evidence-pointers branch from 7abf277 to 64c9bac Compare June 26, 2026 08:22
@github-actions

Copy link
Copy Markdown
Contributor

@github-actions

github-actions Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Recipe evidence check

Protected recipes

Recipes with committed evidence (recipes/evidence/<slug>/<source>/<digest>.yaml) that this PR affects: 2

Recipe Source Pointer Verify Digest match
gb200-eks-ubuntu-training 7c4c0edc8c765a95a0f3afdb3bbb8e91 sha256-8274b6a1da24aa9782dc12162bf6a38265c30a852585ca64cfad5718efbbdec3 ❌ invalid — registry-forbidden (HTTP 401): registry not accessible (make the fork's aicr-evidence package public, or provide registry credentials) ⚠️ skipped (no signed digest)
h100-gke-cos-training 7c4c0edc8c765a95a0f3afdb3bbb8e91 sha256-33d4cf36622ead990c43a596f6f53c62b87d9fa4708f59b7e3f356f215e54317 ❌ invalid — registry-forbidden (HTTP 401): registry not accessible (make the fork's aicr-evidence package public, or provide registry credentials) ⚠️ skipped (no signed digest)

How to refresh evidence

Run on a cluster matching the recipe's criteria:

aicr snapshot -o snapshot.yaml
aicr validate \
  -r recipes/overlays/<slug>.yaml \
  -s snapshot.yaml \
  --emit-attestation ./out \
  --push ghcr.io/<your-fork>/aicr-evidence
# Copy to the per-source path printed in the emit 'copyTo' hint:
#   recipes/evidence/<slug>/<source>/<bundle-digest>.yaml

This gate is warning-only and never blocks merge. See ADR-007 for the trust model.

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown

Review Change Stack

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 9f629a91-636a-4056-9feb-f868e176cafa

📥 Commits

Reviewing files that changed from the base of the PR and between a55d915 and 5d7828a.

📒 Files selected for processing (3)
  • docs/contributor/maintaining.md
  • docs/user/cli-reference.md
  • docs/user/validation.md

📝 Walkthrough

Walkthrough

This PR changes the evidence pointer contract to a per-source layout under recipes/evidence/<recipe>/<source>/<digest>.yaml. It adds source slug generation, allowlist loading and validation, pointer copy-to hints, evidence-tree discovery and contract checking, a GitHub Actions workflow and CLI tool for enforcement, updates the shell check, and refreshes related docs and command help.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • NVIDIA/aicr#873: Overlaps with evidence attestation emission and pointer destination handling in pkg/evidence/attestation.
  • NVIDIA/aicr#1166: Directly related to the recipe-evidence shell check and its discovery/reporting behavior.
  • NVIDIA/aicr#976: Related to the docs updates around emitting recipe evidence and verifying committed pointers.

Suggested labels

area/tests, theme/ci-dx

Suggested reviewers

  • mchmarny
  • lalitadithya
🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise, specific, and matches the PR’s main changes around per-source evidence pointers and the signer allowlist.
Description check ✅ Passed The description accurately summarizes the per-source evidence contract, allowlist, CI enforcement, and related docs/CLI changes.
Linked Issues check ✅ Passed The changes implement the per-source pointer layout, allowlist, anti-squat checks, discovery updates, and supporting docs/CI needed by #1401.
Out of Scope Changes check ✅ Passed The changed files all support the evidence-pointer contract and signer allowlist; no unrelated functionality appears introduced.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 7

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/recipe-evidence-check.sh:
- Around line 240-241: The report table in recipe-evidence-check.sh needs a
pointer identifier so repeated rows from the same source can be distinguished.
Update the row rendering in the main report loop (and any related aggregation
helpers around the table output) to include the pointer basename or signed
digest as its own column, or otherwise collapse duplicates before printing. Make
sure the header and each emitted row in the recipe/source/verify output stay
aligned with the new pointer-specific field.

In `@docs/contributor/maintaining.md`:
- Around line 90-92: The pointer-file requirement is too vague and should
explicitly name the immutable digest-pinned file used for each run. Update the
maintainer guidance to reference the per-source pointer file under
recipes/evidence/<recipe>/<src>/ by its bundle-digest filename pattern, and make
sure the wording in this section clearly ties the contract to the
one-file-per-run pointer expected by the CI gate.

In `@docs/design/007-recipe-evidence.md`:
- Around line 682-685: Add a language tag to the fenced block in the recipe
evidence design doc so markdownlint stops flagging it; update the fence around
the path listing in the relevant section of the documentation to use a text
language identifier, keeping the existing content unchanged.

In `@pkg/evidence/allowlist/allowlist.go`:
- Around line 191-204: `validateEntry` currently only checks that exactly one of
`Source` or `IdentityPattern` is set, but it does not enforce the per-class
rules. Update `validateEntry` in allowlist to reject `IdentityPattern` unless
`class` is first-party, and reject `Source` when `class` is first-party, while
keeping the existing issuer and exclusivity checks. Add negative tests covering
a community/partner entry with `identityPattern` and a first-party entry with
`source` so `Load` fails in both cases.

In `@pkg/evidence/attestation/source.go`:
- Around line 24-28: Increase the source slug width used by source ownership and
allowlist matching, since SourceSlugLength is currently too small for an
authorization key. Update the SourceSlugLength constant in source.go to a
materially larger value (at least 128 bits or the full digest), and verify any
code that depends on this slug, especially Allowlist.Classify and
verifier.checkPointerFile, continues to use the same slug length consistently
for directory ownership and trust-root classification.

In `@pkg/evidence/verifier/discover.go`:
- Around line 81-84: The error handling in discover should preserve the
structured code returned by allowlist.Load instead of always re-wrapping it as
ErrCodeInvalidRequest. Update the allowlist.Load call site in discover.go to use
errors.PropagateOrWrap so existing pkg/errors codes like NotFound or Internal
are passed through unchanged, while still adding the local context message for
non-coded errors. Use the discover logic around allowlist.Load and the existing
errors package pattern as the reference point.
- Around line 175-179: The authorization check in checkPointerFile currently
trusts any allowlist match from al.Classify, but slug-only matches can bypass
issuer validation. Update the check around signer.Issuer and signer.Identity to
require that the returned allowlist entry’s issuer matches signer.Issuer before
accepting it, or adjust allowlist.Classify so the slug fast-path also validates
issuer. Use the existing symbols checkPointerFile, al.Classify, and the
allowlist logic in allowlist.go to locate and fix the flow.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: b6f7ec8a-3e85-40c6-8052-628237d98eea

📥 Commits

Reviewing files that changed from the base of the PR and between 6fc3444 and 7abf277.

📒 Files selected for processing (26)
  • .github/CODEOWNERS
  • .github/scripts/recipe-evidence-check.sh
  • .github/workflows/evidence-pointer-contract.yaml
  • docs/contributor/maintaining.md
  • docs/design/007-recipe-evidence.md
  • docs/integrator/recipe-development.md
  • docs/user/artifact-verification.md
  • docs/user/cli-reference.md
  • docs/user/validation.md
  • pkg/cli/evidence_digest.go
  • pkg/cli/evidence_verify.go
  • pkg/cli/validate.go
  • pkg/evidence/allowlist/allowlist.go
  • pkg/evidence/allowlist/allowlist_test.go
  • pkg/evidence/attestation/emit.go
  • pkg/evidence/attestation/pointer.go
  • pkg/evidence/attestation/pointer_test.go
  • pkg/evidence/attestation/publish.go
  • pkg/evidence/attestation/source.go
  • pkg/evidence/attestation/source_test.go
  • pkg/evidence/verifier/discover.go
  • pkg/evidence/verifier/discover_test.go
  • recipes/evidence/allowlist.yaml
  • recipes/evidence/gb200-eks-ubuntu-training/7c4c0edc8c76/sha256-8274b6a1da24aa9782dc12162bf6a38265c30a852585ca64cfad5718efbbdec3.yaml
  • recipes/evidence/h100-gke-cos-training/7c4c0edc8c76/sha256-33d4cf36622ead990c43a596f6f53c62b87d9fa4708f59b7e3f356f215e54317.yaml
  • tools/evidence-pointercheck/main.go

Comment thread .github/scripts/recipe-evidence-check.sh Outdated
Comment thread docs/contributor/maintaining.md Outdated
Comment thread docs/design/007-recipe-evidence.md Outdated
Comment thread pkg/evidence/allowlist/allowlist.go
Comment thread pkg/evidence/attestation/source.go Outdated
Comment thread pkg/evidence/verifier/discover.go
Comment thread pkg/evidence/verifier/discover.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 11

♻️ Duplicate comments (3)
docs/design/007-recipe-evidence.md (1)

385-394: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Name the digest-pinned file here.

This bullet still points to recipes/evidence/<slug>.yaml, which is not the per-source contract described elsewhere in this ADR. Spell out the immutable filename so the protected-set wording matches the CI/verifier path shape.

Suggested fix
-  iff it has a committed pointer at `recipes/evidence/<slug>.yaml`.
+  iff it has a committed pointer file at
+  `recipes/evidence/<recipe>/<src>/<bundle-digest>.yaml`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/007-recipe-evidence.md` around lines 385 - 394, The “Protected
vs. other affected” bullet still refers to the generic
recipes/evidence/<slug>.yaml pointer, but it should name the immutable
digest-pinned filename used by the CI/verifier contract. Update the wording in
this section to explicitly call out the per-source pinned file path so the
protected-set description matches the rest of the ADR and the verifier path
shape.
docs/contributor/maintaining.md (1)

90-92: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Spell out the digest-pinned filename.

recipes/evidence/<recipe>/<src>/ is still too vague here. The checklist should name the immutable <bundle-digest>.yaml file so it matches the CI gate and the ADR contract.

Suggested fix
-1. **Pointer file present.** At least one per-source pointer under
-   `recipes/evidence/<recipe>/<src>/` exists for every touched overlay.
+1. **Pointer file present.** At least one per-source pointer file under
+   `recipes/evidence/<recipe>/<src>/<bundle-digest>.yaml` exists for every touched overlay.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/contributor/maintaining.md` around lines 90 - 92, The pointer-file
checklist is too vague and should explicitly name the immutable digest-pinned
artifact. Update the “Pointer file present” item in the maintaining guide to
reference the exact `<bundle-digest>.yaml` filename under
`recipes/evidence/<recipe>/<src>/`, matching the CI gate and ADR contract. Keep
the wording aligned with the existing checklist entry so it clearly states that
each touched overlay must have the corresponding digest-pinned pointer file.
docs/integrator/recipe-development.md (1)

658-658: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Spell out the digest-pinned filename.

recipes/evidence/<recipe>/<src>/ is still too vague here. Name the immutable <bundle-digest>.yaml file so maintainers and the CI gate are talking about the same thing.

Suggested fix
-   per-source pointer under `recipes/evidence/<recipe>/<src>/`.
+   per-source pointer file under
+   `recipes/evidence/<recipe>/<src>/<bundle-digest>.yaml`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/integrator/recipe-development.md` at line 658, The reference to the
per-source pointer under recipes/evidence/<recipe>/<src>/ is too vague; update
the documentation in this section to explicitly name the immutable digest-pinned
<bundle-digest>.yaml file so readers and CI are referring to the same artifact.
Use the existing recipe/evidence path wording around the pointer location, but
replace the generic folder-only reference with the exact filename convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/recipe-evidence-check.sh:
- Around line 545-559: The digest status logic in recipe-evidence-check.sh is
using signed_digest even when verification has already failed, which can produce
misleading matches/stale output and extra warnings. Update the digest rendering
block that builds digest_cell so it only compares signed_digest against
current_digest when verify_ok is true, and otherwise emit a
verification-failed/ignored digest state without doing the comparison. Keep the
existing warning counting behavior aligned with the verify_ok branch so a failed
verify does not add a second digest-related warning.
- Around line 71-77: The md_escape helper only strips newline characters, so
verifier-derived values can still include carriage returns and break the
generated Markdown table row. Update md_escape to normalize both newline and
carriage return characters before escaping pipes and removing backticks, keeping
the sanitization logic in the md_escape function consistent for all table-cell
inputs.
- Around line 442-446: The digest computation in the `recipe-evidence-check.sh`
loop still runs `aicr evidence digest` for every protected slug even after the
`MAX_ROWS` cap has effectively been reached. Update the row-processing logic
around `current_digest` and the pointer-row truncation path so the loop stops
invoking the digest command once the cap is hit, while still preserving the
summary/report behavior. Use the existing `MAX_ROWS` control flow and the
per-row handling around the `digest_err`/`current_digest` block to short-circuit
further digest work as soon as no more rows can be emitted.

In `@docs/user/cli-reference.md`:
- Around line 2670-2694: The `aicr evidence sign` docs still point users at the
legacy `recipes/evidence/<recipe>.yaml` target, which is no longer correct under
the per-source pointer contract. Update the synopsis and example text in the
`aicr evidence sign` section to describe the new per-source committed pointer
location and keep the wording aligned with the command’s current contract for
the pointer argument.

In `@pkg/evidence/allowlist/allowlist.go`:
- Around line 121-143: Make allowlist loading cancelable by threading a context
with timeout through the allowlist read path. Update Load in allowlist.go to
accept context.Context and use it to guard the blocking open/read work, then
propagate that context from the only caller in discover.go so the operation can
be bounded and canceled. Keep the existing validation and error wrapping
behavior in Load, but ensure the new context-aware flow is followed from
verifier discovery into allowlist.Load.

In `@pkg/evidence/attestation/pointer_test.go`:
- Around line 87-119: Refactor TestPointerCopyToHint into a table-driven test to
cover both independent cases in one pattern. Keep the existing assertions for
PointerCopyToHint, but express the signed path and unsigned guidance scenarios
as separate table entries with names and expected outcomes. Use the existing
Pointer, PointerAttestation, PointerBundle, and PointerSigner setup within each
case so the test follows the repo’s standard _test.go table-driven style.

In `@pkg/evidence/attestation/pointer.go`:
- Around line 112-115: Update the WritePointerFile docstring to describe the new
per-source layout instead of the old committed pointer path. In the comment near
WritePointerFile, replace the `recipes/evidence/<recipe>.yaml` example with
`recipes/evidence/<recipe>/<source>/<digest>.yaml`, and keep the wording aligned
with the actual in-place patching behavior for committed pointers.
- Around line 122-154: The WritePointerFile helper currently ignores caller
cancellation because it has no context parameter and does not check deadlines
during the temp-file create/write/rename flow. Update WritePointerFile in
pointer.go to accept context.Context and thread it through the pointer
persistence path, using the existing MarshalPointer, os.CreateTemp, tmp.Write,
tmp.Close, and os.Rename steps as checkpoints to abort early when ctx is done.
Make sure every filesystem step returns promptly on cancellation and preserves
the current error-wrapping behavior.

In `@pkg/evidence/attestation/publish.go`:
- Around line 49-53: The NoSign publish flow is logging the wrong copy-to hint
because PointerCopyToHint(pointer) treats an empty signer block as “sign and
push,” even though the bundle has already been pushed. Update the publish path
in publish.go (the NoSign branch and any shared hint helper used there) so the
next-step message distinguishes “unsigned but already pushed” from “not yet
pushed,” and avoid telling users to repeat the push; either special-case
opts.NoSign at the call site or extend PointerCopyToHint to accept that state.

In `@pkg/evidence/verifier/discover.go`:
- Around line 155-183: The pointer validation in checkPointerFile is missing the
filename contract check, so a misnamed YAML file can still be accepted under the
correct recipe/source path. Update checkPointerFile to compare
filepath.Base(path) against the bundle digest declared in the loaded pointer
(the committed pointer name should be <bundle-digest>.yaml), and return an error
when they differ; use the existing LoadAndValidatePointer and ptr fields in
discover.go to enforce this alongside the recipe and source checks.
- Around line 141-147: The directory scan under the evidence tree is too
permissive because the loop in discover.go skips subdirectories and non-“.yaml”
entries, allowing unexpected committed children to evade validation. Update the
file iteration logic that builds TreeProblem entries so every unexpected item
under sourcePath is reported as a problem instead of continuing, and only the
allowed add-only <recipe>/<source>/<bundle-digest>.yaml layout passes. Keep the
existing checkPointerFile path for valid YAML files and add explicit rejection
handling for f.IsDir() and non-YAML names in the same discovery flow.

---

Duplicate comments:
In `@docs/contributor/maintaining.md`:
- Around line 90-92: The pointer-file checklist is too vague and should
explicitly name the immutable digest-pinned artifact. Update the “Pointer file
present” item in the maintaining guide to reference the exact
`<bundle-digest>.yaml` filename under `recipes/evidence/<recipe>/<src>/`,
matching the CI gate and ADR contract. Keep the wording aligned with the
existing checklist entry so it clearly states that each touched overlay must
have the corresponding digest-pinned pointer file.

In `@docs/design/007-recipe-evidence.md`:
- Around line 385-394: The “Protected vs. other affected” bullet still refers to
the generic recipes/evidence/<slug>.yaml pointer, but it should name the
immutable digest-pinned filename used by the CI/verifier contract. Update the
wording in this section to explicitly call out the per-source pinned file path
so the protected-set description matches the rest of the ADR and the verifier
path shape.

In `@docs/integrator/recipe-development.md`:
- Line 658: The reference to the per-source pointer under
recipes/evidence/<recipe>/<src>/ is too vague; update the documentation in this
section to explicitly name the immutable digest-pinned <bundle-digest>.yaml file
so readers and CI are referring to the same artifact. Use the existing
recipe/evidence path wording around the pointer location, but replace the
generic folder-only reference with the exact filename convention.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: c08aa453-1c4e-42a8-b733-d98e2855c4db

📥 Commits

Reviewing files that changed from the base of the PR and between 7abf277 and 64c9bac.

📒 Files selected for processing (26)
  • .github/CODEOWNERS
  • .github/scripts/recipe-evidence-check.sh
  • .github/workflows/evidence-pointer-contract.yaml
  • docs/contributor/maintaining.md
  • docs/design/007-recipe-evidence.md
  • docs/integrator/recipe-development.md
  • docs/user/artifact-verification.md
  • docs/user/cli-reference.md
  • docs/user/validation.md
  • pkg/cli/evidence_digest.go
  • pkg/cli/evidence_verify.go
  • pkg/cli/validate.go
  • pkg/evidence/allowlist/allowlist.go
  • pkg/evidence/allowlist/allowlist_test.go
  • pkg/evidence/attestation/emit.go
  • pkg/evidence/attestation/pointer.go
  • pkg/evidence/attestation/pointer_test.go
  • pkg/evidence/attestation/publish.go
  • pkg/evidence/attestation/source.go
  • pkg/evidence/attestation/source_test.go
  • pkg/evidence/verifier/discover.go
  • pkg/evidence/verifier/discover_test.go
  • recipes/evidence/allowlist.yaml
  • recipes/evidence/gb200-eks-ubuntu-training/7c4c0edc8c76/sha256-8274b6a1da24aa9782dc12162bf6a38265c30a852585ca64cfad5718efbbdec3.yaml
  • recipes/evidence/h100-gke-cos-training/7c4c0edc8c76/sha256-33d4cf36622ead990c43a596f6f53c62b87d9fa4708f59b7e3f356f215e54317.yaml
  • tools/evidence-pointercheck/main.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 11

♻️ Duplicate comments (3)
docs/design/007-recipe-evidence.md (1)

385-394: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Name the digest-pinned file here.

This bullet still points to recipes/evidence/<slug>.yaml, which is not the per-source contract described elsewhere in this ADR. Spell out the immutable filename so the protected-set wording matches the CI/verifier path shape.

Suggested fix
-  iff it has a committed pointer at `recipes/evidence/<slug>.yaml`.
+  iff it has a committed pointer file at
+  `recipes/evidence/<recipe>/<src>/<bundle-digest>.yaml`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/007-recipe-evidence.md` around lines 385 - 394, The “Protected
vs. other affected” bullet still refers to the generic
recipes/evidence/<slug>.yaml pointer, but it should name the immutable
digest-pinned filename used by the CI/verifier contract. Update the wording in
this section to explicitly call out the per-source pinned file path so the
protected-set description matches the rest of the ADR and the verifier path
shape.
docs/contributor/maintaining.md (1)

90-92: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Spell out the digest-pinned filename.

recipes/evidence/<recipe>/<src>/ is still too vague here. The checklist should name the immutable <bundle-digest>.yaml file so it matches the CI gate and the ADR contract.

Suggested fix
-1. **Pointer file present.** At least one per-source pointer under
-   `recipes/evidence/<recipe>/<src>/` exists for every touched overlay.
+1. **Pointer file present.** At least one per-source pointer file under
+   `recipes/evidence/<recipe>/<src>/<bundle-digest>.yaml` exists for every touched overlay.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/contributor/maintaining.md` around lines 90 - 92, The pointer-file
checklist is too vague and should explicitly name the immutable digest-pinned
artifact. Update the “Pointer file present” item in the maintaining guide to
reference the exact `<bundle-digest>.yaml` filename under
`recipes/evidence/<recipe>/<src>/`, matching the CI gate and ADR contract. Keep
the wording aligned with the existing checklist entry so it clearly states that
each touched overlay must have the corresponding digest-pinned pointer file.
docs/integrator/recipe-development.md (1)

658-658: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Spell out the digest-pinned filename.

recipes/evidence/<recipe>/<src>/ is still too vague here. Name the immutable <bundle-digest>.yaml file so maintainers and the CI gate are talking about the same thing.

Suggested fix
-   per-source pointer under `recipes/evidence/<recipe>/<src>/`.
+   per-source pointer file under
+   `recipes/evidence/<recipe>/<src>/<bundle-digest>.yaml`.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/integrator/recipe-development.md` at line 658, The reference to the
per-source pointer under recipes/evidence/<recipe>/<src>/ is too vague; update
the documentation in this section to explicitly name the immutable digest-pinned
<bundle-digest>.yaml file so readers and CI are referring to the same artifact.
Use the existing recipe/evidence path wording around the pointer location, but
replace the generic folder-only reference with the exact filename convention.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/recipe-evidence-check.sh:
- Around line 545-559: The digest status logic in recipe-evidence-check.sh is
using signed_digest even when verification has already failed, which can produce
misleading matches/stale output and extra warnings. Update the digest rendering
block that builds digest_cell so it only compares signed_digest against
current_digest when verify_ok is true, and otherwise emit a
verification-failed/ignored digest state without doing the comparison. Keep the
existing warning counting behavior aligned with the verify_ok branch so a failed
verify does not add a second digest-related warning.
- Around line 71-77: The md_escape helper only strips newline characters, so
verifier-derived values can still include carriage returns and break the
generated Markdown table row. Update md_escape to normalize both newline and
carriage return characters before escaping pipes and removing backticks, keeping
the sanitization logic in the md_escape function consistent for all table-cell
inputs.
- Around line 442-446: The digest computation in the `recipe-evidence-check.sh`
loop still runs `aicr evidence digest` for every protected slug even after the
`MAX_ROWS` cap has effectively been reached. Update the row-processing logic
around `current_digest` and the pointer-row truncation path so the loop stops
invoking the digest command once the cap is hit, while still preserving the
summary/report behavior. Use the existing `MAX_ROWS` control flow and the
per-row handling around the `digest_err`/`current_digest` block to short-circuit
further digest work as soon as no more rows can be emitted.

In `@docs/user/cli-reference.md`:
- Around line 2670-2694: The `aicr evidence sign` docs still point users at the
legacy `recipes/evidence/<recipe>.yaml` target, which is no longer correct under
the per-source pointer contract. Update the synopsis and example text in the
`aicr evidence sign` section to describe the new per-source committed pointer
location and keep the wording aligned with the command’s current contract for
the pointer argument.

In `@pkg/evidence/allowlist/allowlist.go`:
- Around line 121-143: Make allowlist loading cancelable by threading a context
with timeout through the allowlist read path. Update Load in allowlist.go to
accept context.Context and use it to guard the blocking open/read work, then
propagate that context from the only caller in discover.go so the operation can
be bounded and canceled. Keep the existing validation and error wrapping
behavior in Load, but ensure the new context-aware flow is followed from
verifier discovery into allowlist.Load.

In `@pkg/evidence/attestation/pointer_test.go`:
- Around line 87-119: Refactor TestPointerCopyToHint into a table-driven test to
cover both independent cases in one pattern. Keep the existing assertions for
PointerCopyToHint, but express the signed path and unsigned guidance scenarios
as separate table entries with names and expected outcomes. Use the existing
Pointer, PointerAttestation, PointerBundle, and PointerSigner setup within each
case so the test follows the repo’s standard _test.go table-driven style.

In `@pkg/evidence/attestation/pointer.go`:
- Around line 112-115: Update the WritePointerFile docstring to describe the new
per-source layout instead of the old committed pointer path. In the comment near
WritePointerFile, replace the `recipes/evidence/<recipe>.yaml` example with
`recipes/evidence/<recipe>/<source>/<digest>.yaml`, and keep the wording aligned
with the actual in-place patching behavior for committed pointers.
- Around line 122-154: The WritePointerFile helper currently ignores caller
cancellation because it has no context parameter and does not check deadlines
during the temp-file create/write/rename flow. Update WritePointerFile in
pointer.go to accept context.Context and thread it through the pointer
persistence path, using the existing MarshalPointer, os.CreateTemp, tmp.Write,
tmp.Close, and os.Rename steps as checkpoints to abort early when ctx is done.
Make sure every filesystem step returns promptly on cancellation and preserves
the current error-wrapping behavior.

In `@pkg/evidence/attestation/publish.go`:
- Around line 49-53: The NoSign publish flow is logging the wrong copy-to hint
because PointerCopyToHint(pointer) treats an empty signer block as “sign and
push,” even though the bundle has already been pushed. Update the publish path
in publish.go (the NoSign branch and any shared hint helper used there) so the
next-step message distinguishes “unsigned but already pushed” from “not yet
pushed,” and avoid telling users to repeat the push; either special-case
opts.NoSign at the call site or extend PointerCopyToHint to accept that state.

In `@pkg/evidence/verifier/discover.go`:
- Around line 155-183: The pointer validation in checkPointerFile is missing the
filename contract check, so a misnamed YAML file can still be accepted under the
correct recipe/source path. Update checkPointerFile to compare
filepath.Base(path) against the bundle digest declared in the loaded pointer
(the committed pointer name should be <bundle-digest>.yaml), and return an error
when they differ; use the existing LoadAndValidatePointer and ptr fields in
discover.go to enforce this alongside the recipe and source checks.
- Around line 141-147: The directory scan under the evidence tree is too
permissive because the loop in discover.go skips subdirectories and non-“.yaml”
entries, allowing unexpected committed children to evade validation. Update the
file iteration logic that builds TreeProblem entries so every unexpected item
under sourcePath is reported as a problem instead of continuing, and only the
allowed add-only <recipe>/<source>/<bundle-digest>.yaml layout passes. Keep the
existing checkPointerFile path for valid YAML files and add explicit rejection
handling for f.IsDir() and non-YAML names in the same discovery flow.

---

Duplicate comments:
In `@docs/contributor/maintaining.md`:
- Around line 90-92: The pointer-file checklist is too vague and should
explicitly name the immutable digest-pinned artifact. Update the “Pointer file
present” item in the maintaining guide to reference the exact
`<bundle-digest>.yaml` filename under `recipes/evidence/<recipe>/<src>/`,
matching the CI gate and ADR contract. Keep the wording aligned with the
existing checklist entry so it clearly states that each touched overlay must
have the corresponding digest-pinned pointer file.

In `@docs/design/007-recipe-evidence.md`:
- Around line 385-394: The “Protected vs. other affected” bullet still refers to
the generic recipes/evidence/<slug>.yaml pointer, but it should name the
immutable digest-pinned filename used by the CI/verifier contract. Update the
wording in this section to explicitly call out the per-source pinned file path
so the protected-set description matches the rest of the ADR and the verifier
path shape.

In `@docs/integrator/recipe-development.md`:
- Line 658: The reference to the per-source pointer under
recipes/evidence/<recipe>/<src>/ is too vague; update the documentation in this
section to explicitly name the immutable digest-pinned <bundle-digest>.yaml file
so readers and CI are referring to the same artifact. Use the existing
recipe/evidence path wording around the pointer location, but replace the
generic folder-only reference with the exact filename convention.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: c08aa453-1c4e-42a8-b733-d98e2855c4db

📥 Commits

Reviewing files that changed from the base of the PR and between 7abf277 and 64c9bac.

📒 Files selected for processing (26)
  • .github/CODEOWNERS
  • .github/scripts/recipe-evidence-check.sh
  • .github/workflows/evidence-pointer-contract.yaml
  • docs/contributor/maintaining.md
  • docs/design/007-recipe-evidence.md
  • docs/integrator/recipe-development.md
  • docs/user/artifact-verification.md
  • docs/user/cli-reference.md
  • docs/user/validation.md
  • pkg/cli/evidence_digest.go
  • pkg/cli/evidence_verify.go
  • pkg/cli/validate.go
  • pkg/evidence/allowlist/allowlist.go
  • pkg/evidence/allowlist/allowlist_test.go
  • pkg/evidence/attestation/emit.go
  • pkg/evidence/attestation/pointer.go
  • pkg/evidence/attestation/pointer_test.go
  • pkg/evidence/attestation/publish.go
  • pkg/evidence/attestation/source.go
  • pkg/evidence/attestation/source_test.go
  • pkg/evidence/verifier/discover.go
  • pkg/evidence/verifier/discover_test.go
  • recipes/evidence/allowlist.yaml
  • recipes/evidence/gb200-eks-ubuntu-training/7c4c0edc8c76/sha256-8274b6a1da24aa9782dc12162bf6a38265c30a852585ca64cfad5718efbbdec3.yaml
  • recipes/evidence/h100-gke-cos-training/7c4c0edc8c76/sha256-33d4cf36622ead990c43a596f6f53c62b87d9fa4708f59b7e3f356f215e54317.yaml
  • tools/evidence-pointercheck/main.go
🛑 Comments failed to post (11)
.github/scripts/recipe-evidence-check.sh (3)

71-77: 🔒 Security & Privacy | 🟡 Minor | ⚡ Quick win

Collapse carriage returns in Markdown table values.

Line 76 only replaces \n; verifier-derived hints can still carry \r and disrupt the generated Markdown row.

Proposed fix
 md_escape() {
-  printf '%s' "$1" | tr '\n' ' ' | sed 's/|/\\|/g; s/`//g'
+  printf '%s' "$1" | tr '\n\r' '  ' | sed 's/|/\\|/g; s/`//g'
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

# md_escape sanitizes a value rendered into a Markdown table cell. Slugs and
# verifier-supplied hints can in principle carry characters that break the
# table or the surrounding backticks; collapse newlines, escape pipes, and
# drop backticks so a value can't break out of a code span.
md_escape() {
  printf '%s' "$1" | tr '\n\r' '  ' | sed 's/|/\\|/g; s/`//g'
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/recipe-evidence-check.sh around lines 71 - 77, The md_escape
helper only strips newline characters, so verifier-derived values can still
include carriage returns and break the generated Markdown table row. Update
md_escape to normalize both newline and carriage return characters before
escaping pipes and removing backticks, keeping the sanitization logic in the
md_escape function consistent for all table-cell inputs.

442-446: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Skip digest computation after the row cap is reached.

After MAX_ROWS, this loop still runs aicr evidence digest for each remaining protected slug before truncating pointer rows. On broad-impact PRs this can waste CI time despite the report cap.

Proposed fix
   # Compute the recipe's current canonical digest once; every per-source row
   # compares its signed digest against this.
+  if [[ "$rows_written" -ge "$MAX_ROWS" ]]; then
+    rows_truncated=$((rows_truncated + ${`#pointers`[@]}))
+    continue
+  fi
   digest_err=$(mktemp)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

  # Compute the recipe's current canonical digest once; every per-source row
  # compares its signed digest against this.
  if [[ "$rows_written" -ge "$MAX_ROWS" ]]; then
    rows_truncated=$((rows_truncated + ${`#pointers`[@]}))
    continue
  fi
  digest_err=$(mktemp)
  current_digest=""
  if ! current_digest=$(timeout "$AICR_TIMEOUT" "$AICR" evidence digest -r "$overlay" 2>"$digest_err"); then
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/recipe-evidence-check.sh around lines 442 - 446, The digest
computation in the `recipe-evidence-check.sh` loop still runs `aicr evidence
digest` for every protected slug even after the `MAX_ROWS` cap has effectively
been reached. Update the row-processing logic around `current_digest` and the
pointer-row truncation path so the loop stops invoking the digest command once
the cap is hit, while still preserving the summary/report behavior. Use the
existing `MAX_ROWS` control flow and the per-row handling around the
`digest_err`/`current_digest` block to short-circuit further digest work as soon
as no more rows can be emitted.

545-559: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Don’t render digest trust when verification failed.

If verify_ok=false but signed_digest is present, this still reports matches or stale from an invalid verification result. Skip the digest comparison after verify failure to avoid misleading rows and duplicate warnings.

Proposed fix
-    if [[ -n "$signed_digest" ]]; then
+    if [[ "$verify_ok" != "true" ]]; then
+      digest_cell="—"
+    elif [[ -n "$signed_digest" ]]; then
       if [[ "$signed_digest" == "$current_digest" ]]; then
         digest_cell=":white_check_mark: matches"
       else
         digest_cell=":warning: stale (\`${signed_digest:0:12}…\` vs current \`${current_digest:0:12}…\`)"
         warnings=$((warnings + 1))
       fi
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

    if [[ "$verify_ok" != "true" ]]; then
      digest_cell="—"
    elif [[ -n "$signed_digest" ]]; then
      if [[ "$signed_digest" == "$current_digest" ]]; then
        digest_cell=":white_check_mark: matches"
      else
        digest_cell=":warning: stale (\`${signed_digest:0:12}…\` vs current \`${current_digest:0:12}…\`)"
        warnings=$((warnings + 1))
      fi
    else
      digest_cell=":warning: skipped (no signed digest)"
      # Only a warning when the bundle otherwise verified — a failed verify
      # already counted its single warning above.
      if [[ "$verify_ok" == "true" ]]; then
        warnings=$((warnings + 1))
      fi
    fi
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/scripts/recipe-evidence-check.sh around lines 545 - 559, The digest
status logic in recipe-evidence-check.sh is using signed_digest even when
verification has already failed, which can produce misleading matches/stale
output and extra warnings. Update the digest rendering block that builds
digest_cell so it only compares signed_digest against current_digest when
verify_ok is true, and otherwise emit a verification-failed/ignored digest state
without doing the comparison. Keep the existing warning counting behavior
aligned with the verify_ok branch so a failed verify does not add a second
digest-related warning.
docs/user/cli-reference.md (1)

2670-2694: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Update the aicr evidence sign pointer path.

The synopsis/example still reference the legacy recipes/evidence/<recipe>.yaml layout. That will send users to the wrong commit target under the new per-source contract.

Fix
-The positional `<pointer>` is the committed `recipes/evidence/<recipe>.yaml`.
+The positional `<pointer>` is the committed `recipes/evidence/<recipe>/<source>/<digest>.yaml`.
...
-aicr evidence sign recipes/evidence/h100-eks-ubuntu-training.yaml
+aicr evidence sign recipes/evidence/h100-eks-ubuntu-training/<src>/<digest>.yaml

Based on the per-source pointer contract in the PR objectives and line-range notes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

The positional `<pointer>` is the committed `recipes/evidence/<recipe>/<source>/<digest>.yaml`. The pointer must carry exactly one attestation that is already pushed (`bundle.oci`/`bundle.digest` set) and not yet signed (empty `signer`); otherwise the command fails closed (an already-signed pointer is never re-signed).

**Flags:**

| Flag | Alias | Type | Default | Description |
|------|-------|------|---------|-------------|
| `--identity-token` | | string | | Pre-fetched OIDC identity token for keyless signing. Skips ambient/browser/device-code flows. Reads `COSIGN_IDENTITY_TOKEN` from env. Same precedence chain as `aicr evidence publish`. |
| `--oidc-device-flow` | | bool | `false` | Use the OAuth 2.0 device authorization grant for OIDC instead of opening a browser callback. Reads `AICR_OIDC_DEVICE_FLOW`. Useful on headless hosts. |
| `--yes` | `--assume-yes` | bool | `false` | Skip the interactive confirmation shown before keyless signing publishes your OIDC identity (browser/device-code paths only; the banner is still printed). Reads `AICR_ASSUME_YES`. |
| `--plain-http` | | bool | `false` | Use HTTP instead of HTTPS for the registry (pull + referrer attach; local-registry tests). |
| `--insecure-tls` | | bool | `false` | Skip TLS verification for the registry (pull + referrer attach; self-signed registries). |

**Exit codes:**

| Code | Meaning |
|------|---------|
| 0 | Bundle signed, referrer attached, and the pointer's `signer` block written back. |
| non-zero | Pointer already signed / has nothing pushed to sign, bundle could not be pulled (e.g. a private registry returns 403), identity-disclosure prompt declined, or signing/attach failed. |

**Examples:**

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/user/cli-reference.md` around lines 2670 - 2694, The `aicr evidence
sign` docs still point users at the legacy `recipes/evidence/<recipe>.yaml`
target, which is no longer correct under the per-source pointer contract. Update
the synopsis and example text in the `aicr evidence sign` section to describe
the new per-source committed pointer location and keep the wording aligned with
the command’s current contract for the pointer argument.
pkg/evidence/allowlist/allowlist.go (1)

121-143: 🩺 Stability & Availability | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files pkg/evidence/allowlist/allowlist.go && wc -l pkg/evidence/allowlist/allowlist.go && sed -n '1,220p' pkg/evidence/allowlist/allowlist.go

Repository: NVIDIA/aicr

Length of output: 8164


🏁 Script executed:

rg -n "\bLoad\(" pkg/evidence -g '!**/*_test.go' && printf '\n---\n' && rg -n "context\.Context|WithTimeout|ReadAll|Open\(" pkg/evidence/allowlist/allowlist.go pkg/evidence -g '!**/*_test.go'

Repository: NVIDIA/aicr

Length of output: 6570


🏁 Script executed:

sed -n '220,420p' pkg/evidence/allowlist/allowlist.go

Repository: NVIDIA/aicr

Length of output: 6544


🏁 Script executed:

rg -n "allowlist\.Load|LoadAllowlist|allowlist" pkg -g '!**/*_test.go'

Repository: NVIDIA/aicr

Length of output: 10194


🏁 Script executed:

python3 - <<'PY'
from pathlib import Path
import re

root = Path('.')
paths = [p for p in root.rglob('*.go') if 'vendor' not in p.parts]
targets = []
for p in paths:
    try:
        text = p.read_text()
    except Exception:
        continue
    if re.search(r'\ballowlist\.Load\s*\(', text) or re.search(r'\bLoad\s*\(\s*path string\s*\)\s*\(\*Allowlist,\s*error\)', text):
        targets.append(str(p))
print("\n".join(sorted(set(targets))))
PY

Repository: NVIDIA/aicr

Length of output: 219


Make allowlist loading cancelable. pkg/evidence/allowlist/allowlist.go:121 does blocking os.Open/io.ReadAll with no context.Context, and the only caller (pkg/evidence/verifier/discover.go:81) also lacks one. Thread a timeout-bounded context through this path.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/evidence/allowlist/allowlist.go` around lines 121 - 143, Make allowlist
loading cancelable by threading a context with timeout through the allowlist
read path. Update Load in allowlist.go to accept context.Context and use it to
guard the blocking open/read work, then propagate that context from the only
caller in discover.go so the operation can be bounded and canceled. Keep the
existing validation and error wrapping behavior in Load, but ensure the new
context-aware flow is followed from verifier discovery into allowlist.Load.

Source: Coding guidelines

pkg/evidence/attestation/pointer_test.go (1)

87-119: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use a table-driven test here.

This function is validating two independent cases (signed path and unsigned guidance), so it should use the repo's table-driven test pattern. As per coding guidelines, **/*_test.go: Write table-driven tests for multiple test cases.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/evidence/attestation/pointer_test.go` around lines 87 - 119, Refactor
TestPointerCopyToHint into a table-driven test to cover both independent cases
in one pattern. Keep the existing assertions for PointerCopyToHint, but express
the signed path and unsigned guidance scenarios as separate table entries with
names and expected outcomes. Use the existing Pointer, PointerAttestation,
PointerBundle, and PointerSigner setup within each case so the test follows the
repo’s standard _test.go table-driven style.

Source: Coding guidelines

pkg/evidence/attestation/pointer.go (2)

112-115: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Update this docstring to the new per-source layout.

The comment still points to recipes/evidence/<recipe>.yaml, but this PR's contract is recipes/evidence/<recipe>/<source>/<digest>.yaml. Leaving the old path here will send future callers to the wrong on-disk location.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/evidence/attestation/pointer.go` around lines 112 - 115, Update the
WritePointerFile docstring to describe the new per-source layout instead of the
old committed pointer path. In the comment near WritePointerFile, replace the
`recipes/evidence/<recipe>.yaml` example with
`recipes/evidence/<recipe>/<source>/<digest>.yaml`, and keep the wording aligned
with the actual in-place patching behavior for committed pointers.

122-154: 🩺 Stability & Availability | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# Inspect the attestation pointer implementation and its callers.
set -e
git ls-files 'pkg/evidence/attestation/*' 'pkg/evidence/**' | sed -n '1,200p'
ast-grep outline pkg/evidence/attestation/pointer.go --view expanded || true
wc -l pkg/evidence/attestation/pointer.go
cat -n pkg/evidence/attestation/pointer.go | sed -n '1,240p'
rg -n "WritePointerFile\(" -S .

Repository: NVIDIA/aicr

Length of output: 10608


🏁 Script executed:

sed -n '1,260p' pkg/evidence/attestation/sign.go
printf '\n---\n'
sed -n '1,260p' pkg/evidence/attestation/publish.go
printf '\n---\n'
sed -n '1,260p' pkg/evidence/attestation/emit.go

Repository: NVIDIA/aicr

Length of output: 27043


🏁 Script executed:

sed -n '1,260p' pkg/evidence/attestation/sign.go
printf '\n---\n'
sed -n '1,260p' pkg/evidence/attestation/publish.go
printf '\n---\n'
sed -n '1,260p' pkg/evidence/attestation/emit.go

Repository: NVIDIA/aicr

Length of output: 27043


🏁 Script executed:

rg -n "context\.WithTimeout|CreateTemp\(|os\.Rename\(|io\.ReadAll\(|WritePointerFile\(" pkg/evidence pkg -g '!**/*_test.go'

Repository: NVIDIA/aicr

Length of output: 15831


🏁 Script executed:

rg -n "context\.WithTimeout|CreateTemp\(|os\.Rename\(|io\.ReadAll\(|WritePointerFile\(" pkg/evidence pkg -g '!**/*_test.go'

Repository: NVIDIA/aicr

Length of output: 15831


🏁 Script executed:

rg -n "context\\.WithTimeout|CreateTemp\\(|os\\.Rename\\(|io\\.ReadAll\\(|WritePointerFile\\(" pkg/evidence pkg -g '!**/*_test.go'

Repository: NVIDIA/aicr

Length of output: 15831


🏁 Script executed:

rg -n "context\\.WithTimeout|CreateTemp\\(|os\\.Rename\\(|io\\.ReadAll\\(|WritePointerFile\\(" pkg/evidence pkg -g '!**/*_test.go'

Repository: NVIDIA/aicr

Length of output: 15831


Thread context.Context through WritePointerFile. The temp-file create/write/rename path drops the caller’s deadline, so pointer persistence can’t be canceled alongside the rest of the evidence workflow. Pass ctx into this helper and check it before each filesystem step.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/evidence/attestation/pointer.go` around lines 122 - 154, The
WritePointerFile helper currently ignores caller cancellation because it has no
context parameter and does not check deadlines during the temp-file
create/write/rename flow. Update WritePointerFile in pointer.go to accept
context.Context and thread it through the pointer persistence path, using the
existing MarshalPointer, os.CreateTemp, tmp.Write, tmp.Close, and os.Rename
steps as checkpoints to abort early when ctx is done. Make sure every filesystem
step returns promptly on cancellation and preserves the current error-wrapping
behavior.

Source: Coding guidelines

pkg/evidence/attestation/publish.go (1)

49-53: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Fix the copyTo hint for NoSign publishes.

With NoSign, this path now logs PointerCopyToHint(pointer), but that helper falls back to “sign and push” whenever the signer block is empty. In the new workflow the bundle is already pushed, so the logged next step is wrong and tells users to repeat an unnecessary push. Either special-case opts.NoSign here or teach the helper to distinguish “unsigned but already pushed” from “not yet pushed”.

Suggested fix
-	slog.Info("evidence pointer written",
-		"path", pointerPath,
-		"copyTo", PointerCopyToHint(pointer))
+	copyTo := PointerCopyToHint(pointer)
+	if opts.NoSign && out.PushSummary != nil {
+		copyTo = "(sign the pushed bundle, then commit under recipes/evidence/<recipe>/<source>/)"
+	}
+	slog.Info("evidence pointer written",
+		"path", pointerPath,
+		"copyTo", copyTo)

Also applies to: 125-127

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/evidence/attestation/publish.go` around lines 49 - 53, The NoSign publish
flow is logging the wrong copy-to hint because PointerCopyToHint(pointer) treats
an empty signer block as “sign and push,” even though the bundle has already
been pushed. Update the publish path in publish.go (the NoSign branch and any
shared hint helper used there) so the next-step message distinguishes “unsigned
but already pushed” from “not yet pushed,” and avoid telling users to repeat the
push; either special-case opts.NoSign at the call site or extend
PointerCopyToHint to accept that state.
pkg/evidence/verifier/discover.go (2)

141-147: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject unexpected children under <recipe>/<source>/.

Line 142 silently skips subdirectories and non-.yaml files. That lets a committed file like recipes/evidence/<recipe>/<source>/nested/evil.yaml evade this gate entirely even though the contract is supposed to cover every committed pointer under the evidence tree. Treat unexpected entries here as problems instead of continue.

Based on the PR objectives, the allowed layout is the add-only <recipe>/<source>/<bundle-digest>.yaml contract.

Proposed fix
 	for _, f := range files {
-		if f.IsDir() || !strings.HasSuffix(f.Name(), ".yaml") {
+		if f.IsDir() {
+			problems = append(problems, TreeProblem{
+				Path:    filepath.Join(sourcePath, f.Name()),
+				Message: "unexpected nested directory under <recipe>/<source>/",
+			})
+			continue
+		}
+		if !strings.HasSuffix(f.Name(), ".yaml") {
+			problems = append(problems, TreeProblem{
+				Path:    filepath.Join(sourcePath, f.Name()),
+				Message: "unexpected file under <recipe>/<source>/; pointers must be named <bundle-digest>.yaml",
+			})
 			continue
 		}
 		path := filepath.Join(sourcePath, f.Name())
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

	for _, f := range files {
		if f.IsDir() {
			problems = append(problems, TreeProblem{
				Path:    filepath.Join(sourcePath, f.Name()),
				Message: "unexpected nested directory under <recipe>/<source>/",
			})
			continue
		}
		if !strings.HasSuffix(f.Name(), ".yaml") {
			problems = append(problems, TreeProblem{
				Path:    filepath.Join(sourcePath, f.Name()),
				Message: "unexpected file under <recipe>/<source>/; pointers must be named <bundle-digest>.yaml",
			})
			continue
		}
		path := filepath.Join(sourcePath, f.Name())
		if msg := checkPointerFile(path, recipe, source, al); msg != "" {
			problems = append(problems, TreeProblem{Path: path, Message: msg})
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/evidence/verifier/discover.go` around lines 141 - 147, The directory scan
under the evidence tree is too permissive because the loop in discover.go skips
subdirectories and non-“.yaml” entries, allowing unexpected committed children
to evade validation. Update the file iteration logic that builds TreeProblem
entries so every unexpected item under sourcePath is reported as a problem
instead of continuing, and only the allowed add-only
<recipe>/<source>/<bundle-digest>.yaml layout passes. Keep the existing
checkPointerFile path for valid YAML files and add explicit rejection handling
for f.IsDir() and non-YAML names in the same discovery flow.

155-183: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Enforce the <bundle-digest>.yaml filename contract.

checkPointerFile verifies recipe and source, but it never compares filepath.Base(path) with the digest declared in the pointer. anything.yaml under the right <recipe>/<source>/ therefore passes, which weakens the immutable per-run path contract and allows misnamed or duplicate pointers for the same bundle.

Based on the PR objectives, each committed pointer path is part of the contract and should be <bundle-digest>.yaml.

Proposed fix
 	if wantSource != source {
 		return "signer " + signer.Identity + " maps to source " + wantSource +
 			" but the pointer is committed under " + source + " (path ownership / squat)"
 	}
+	wantName := strings.ReplaceAll(ptr.Attestations[0].Bundle.Digest, ":", "-") + ".yaml"
+	if filepath.Base(path) != wantName {
+		return "pointer filename " + filepath.Base(path) + " does not match bundle digest " + wantName
+	}
 	class, _, ok := al.Classify(signer.Issuer, signer.Identity)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

func checkPointerFile(path, recipe, source string, al *allowlist.Allowlist) string {
	ptr, err := LoadAndValidatePointer(path)
	if err != nil {
		return "invalid pointer: " + err.Error()
	}
	if ptr.Recipe != recipe {
		return "pointer.recipe " + ptr.Recipe + " does not match recipe directory " + recipe
	}
	signer := ptr.Attestations[0].Signer
	if signer == nil {
		return "committed pointer must be signed (attestation has no signer)"
	}
	wantSource, err := attestation.SourceSlug(signer.Issuer, signer.Identity)
	if err != nil {
		return "cannot derive source slug from signer: " + err.Error()
	}
	if wantSource != source {
		return "signer " + signer.Identity + " maps to source " + wantSource +
			" but the pointer is committed under " + source + " (path ownership / squat)"
	}
	wantName := strings.ReplaceAll(ptr.Attestations[0].Bundle.Digest, ":", "-") + ".yaml"
	if filepath.Base(path) != wantName {
		return "pointer filename " + filepath.Base(path) + " does not match bundle digest " + wantName
	}
	class, _, ok := al.Classify(signer.Issuer, signer.Identity)
	if !ok {
		return "signer " + signer.Identity + " (issuer " + signer.Issuer +
			") is not in the allowlist; add a community/partner entry to recipes/evidence/allowlist.yaml"
	}
	if class == allowlist.ClassFirstParty {
		return "first-party signer must ingest directly, not commit a per-run pointer"
	}
	return ""
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/evidence/verifier/discover.go` around lines 155 - 183, The pointer
validation in checkPointerFile is missing the filename contract check, so a
misnamed YAML file can still be accepted under the correct recipe/source path.
Update checkPointerFile to compare filepath.Base(path) against the bundle digest
declared in the loaded pointer (the committed pointer name should be
<bundle-digest>.yaml), and return an error when they differ; use the existing
LoadAndValidatePointer and ptr fields in discover.go to enforce this alongside
the recipe and source checks.

… per-class allowlist)

Remediate CodeRabbit review on the per-source pointer contract:

- Widen SourceSlugLength 12->32 (48->128 bit). The slug is an authorization
  key (allowlist.Classify + verifier path-ownership), so it must resist a
  deliberate second-preimage squat. Rename committed evidence dirs and update
  fixtures/docs/allowlist accordingly; leave the unrelated OCI manifest
  fingerprint at 12 hex.
- Anchor allowlist Classify slug match on issuer as well, so a colliding slug
  under a different issuer cannot inherit an entry.
- Enforce per-class key kind in validateEntry: first-party uses identityPattern,
  community/partner use source; add negative tests.
- Preserve allowlist.Load's coded errors in CheckEvidenceTree via PropagateOrWrap.
- Add a Pointer column to the recipe-evidence report so multiple pointers under
  one source stay distinguishable.
- Docs: name the <bundle-digest>.yaml pointer file in maintaining.md and add a
  language tag to the ADR-007 path fence.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

♻️ Duplicate comments (2)
docs/contributor/maintaining.md (1)

242-245: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add a language tag to this fence.

markdownlint-cli2 will flag the bare fenced block here; text is enough for a path listing.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/contributor/maintaining.md` around lines 242 - 245, The fenced block in
the contributor maintenance docs is missing a language tag and will be flagged
by markdownlint-cli2; update that specific fence in the maintaining guide to use
a text language label since it contains a path listing. Locate the bare markdown
fence in the docs/maintaining section and add the appropriate language
identifier without changing the content.
docs/design/007-recipe-evidence.md (1)

242-245: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Tag both path-listing fences as text.

These bare fences will trip markdownlint; text keeps the snippet readable and lint-clean.

Also applies to: 717-720

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/design/007-recipe-evidence.md` around lines 242 - 245, The markdown
examples use bare fenced code blocks, which will trip markdownlint; update the
fenced snippets in the referenced example sections to explicitly declare text as
the language while keeping the existing content unchanged. Use the nearby
snippet markers around the registry and certificateIdentityRegexp examples, and
apply the same fix to the other mentioned path-listing fence as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/scripts/recipe-evidence-check.sh:
- Around line 410-411: The report table header is currently only printed to
stdout, while the row entries are appended to the report file, so the report can
miss its Markdown header. Update the header emission in the table-building
section of recipe-evidence-check.sh so the same output path used by the later
row writes is also used for the header lines, keeping the report structure
consistent.

---

Duplicate comments:
In `@docs/contributor/maintaining.md`:
- Around line 242-245: The fenced block in the contributor maintenance docs is
missing a language tag and will be flagged by markdownlint-cli2; update that
specific fence in the maintaining guide to use a text language label since it
contains a path listing. Locate the bare markdown fence in the docs/maintaining
section and add the appropriate language identifier without changing the
content.

In `@docs/design/007-recipe-evidence.md`:
- Around line 242-245: The markdown examples use bare fenced code blocks, which
will trip markdownlint; update the fenced snippets in the referenced example
sections to explicitly declare text as the language while keeping the existing
content unchanged. Use the nearby snippet markers around the registry and
certificateIdentityRegexp examples, and apply the same fix to the other
mentioned path-listing fence as well.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 383d1c91-fef4-4295-ac45-6c9434acb2ef

📥 Commits

Reviewing files that changed from the base of the PR and between 64c9bac and a55d915.

📒 Files selected for processing (15)
  • .github/scripts/recipe-evidence-check.sh
  • docs/contributor/maintaining.md
  • docs/design/007-recipe-evidence.md
  • docs/user/artifact-verification.md
  • docs/user/cli-reference.md
  • pkg/evidence/allowlist/allowlist.go
  • pkg/evidence/allowlist/allowlist_test.go
  • pkg/evidence/attestation/pointer_test.go
  • pkg/evidence/attestation/source.go
  • pkg/evidence/attestation/source_test.go
  • pkg/evidence/verifier/discover.go
  • pkg/evidence/verifier/discover_test.go
  • recipes/evidence/allowlist.yaml
  • recipes/evidence/gb200-eks-ubuntu-training/7c4c0edc8c765a95a0f3afdb3bbb8e91/sha256-8274b6a1da24aa9782dc12162bf6a38265c30a852585ca64cfad5718efbbdec3.yaml
  • recipes/evidence/h100-gke-cos-training/7c4c0edc8c765a95a0f3afdb3bbb8e91/sha256-33d4cf36622ead990c43a596f6f53c62b87d9fa4708f59b7e3f356f215e54317.yaml

Comment thread .github/scripts/recipe-evidence-check.sh

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/user/cli-reference.md`:
- Line 389: Update the `--service` option description in the CLI reference to
include `any` in the supported values list, since `service=any` is already used
for self-managed clusters in the validation flow. Locate the existing
`--service` table entry in the CLI reference and expand the allowed service
values alongside the current list so the documented validation matches the
`service=any` example used elsewhere.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 9f629a91-636a-4056-9feb-f868e176cafa

📥 Commits

Reviewing files that changed from the base of the PR and between a55d915 and 5d7828a.

📒 Files selected for processing (3)
  • docs/contributor/maintaining.md
  • docs/user/cli-reference.md
  • docs/user/validation.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Caution

Inline review comments failed to post. This is likely due to GitHub's internal server error or limits when posting large numbers of comments. If you are seeing this consistently it is likely a permissions issue. Please check "Moderation" -> "Code review limits" under your organization settings.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/user/cli-reference.md`:
- Line 389: Update the `--service` option description in the CLI reference to
include `any` in the supported values list, since `service=any` is already used
for self-managed clusters in the validation flow. Locate the existing
`--service` table entry in the CLI reference and expand the allowed service
values alongside the current list so the documented validation matches the
`service=any` example used elsewhere.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 9f629a91-636a-4056-9feb-f868e176cafa

📥 Commits

Reviewing files that changed from the base of the PR and between a55d915 and 5d7828a.

📒 Files selected for processing (3)
  • docs/contributor/maintaining.md
  • docs/user/cli-reference.md
  • docs/user/validation.md
🛑 Comments failed to post (1)
docs/user/cli-reference.md (1)

389-389: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add any to the supported --service list.

The validation walkthrough already uses service=any for self-managed clusters, so omitting it here makes a valid input look unsupported. Based on the service=any example in docs/user/validation.md.

Suggested fix
-| `--service` | | string | K8s service: eks, gke, aks, oke, ocp, kind, lke, bcm |
+| `--service` | | string | K8s service: eks, gke, aks, oke, ocp, any, kind, lke, bcm |
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

| `--service` | | string | K8s service: eks, gke, aks, oke, ocp, any, kind, lke, bcm |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/user/cli-reference.md` at line 389, Update the `--service` option
description in the CLI reference to include `any` in the supported values list,
since `service=any` is already used for self-managed clusters in the validation
flow. Locate the existing `--service` table entry in the CLI reference and
expand the allowed service values alongside the current list so the documented
validation matches the `service=any` example used elsewhere.

@njhensley njhensley marked this pull request as ready for review June 26, 2026 17:37

@mchmarny mchmarny left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Trust-root code that fails closed correctly. The source slug is non-squattable (128-bit SHA over issuer\nidentity with a domain separator), the allowlist loader strict-decodes and rejects any cleartext identity:, reads are bounded with io.LimitReader, and the path-ownership gate enforces content-addressed ownership (recomputed slug equality) rather than a .. substring scan — so a pointer cannot be placed under another party's directory and traversal is structurally impossible. Test coverage exercises the security-critical shapes: squat, unsigned, unlisted, recipe-mismatch, loose-file, over-broad pattern, two-sources-one-recipe, and domain-separation.

No actionable findings — the remaining observations are informational (slug width is sound against second-preimage; the ctx-less CLI reads fall within the project's CLI/test carve-out and match the existing LoadAndValidatePointer signature).

One CI note: the lone red check (KWOK Test Summaryeks-inference (flux-git)) is an unrelated flake — this PR touches no recipe/registry/overlay/component file, and all five sibling eks-inference deployer legs (argocd-git, argocd-helm-oci, argocd-oci, flux-oci, helm) passed. Worth a re-run; not a code signal.

LGTM.

@njhensley njhensley enabled auto-merge (squash) June 26, 2026 18:39
@njhensley njhensley merged commit a2e0690 into NVIDIA:main Jun 26, 2026
159 checks passed
@njhensley njhensley deleted the feat/per-source-evidence-pointers branch June 26, 2026 18:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

GP1 — Per-source evidence-pointer contract (#1347) + first-party direct-ingest + signer allowlist

2 participants