Skip to content

fix(recipe): enforce stated-criteria coverage on recipe resolution#1784

Merged
mchmarny merged 14 commits into
mainfrom
fix/1542-criteria-coverage
Jul 20, 2026
Merged

fix(recipe): enforce stated-criteria coverage on recipe resolution#1784
mchmarny merged 14 commits into
mainfrom
fix/1542-criteria-coverage

Conversation

@mchmarny

Copy link
Copy Markdown
Member

Summary

Recipe resolution now fails with an actionable ErrCodeInvalidRequest (HTTP 400) when a query-stated criteria dimension is not honored by any applied overlay, instead of silently returning a partial recipe that echoes criteria it did not apply. Constraint evaluation additionally fails closed on non-NotFound evaluator errors.

Motivation / Context

The OS-required guard (requireOSIfNeeded) models only service+accelerator/OS, while the criteria matcher treats all dimensions uniformly — so a stated platform or intent could silently contribute nothing. Live repro on main: aicr recipe --service eks --accelerator h100 --intent training --platform kubeflow exited 0 with no kubeflow-trainer content while echoing platform: kubeflow in its criteria.

Fixes: #1542
Related: #1781, #1782, #1783 (follow-ups: nodes demotion, guard subsumption, constraint-path load-time validation)

Type of Change

  • Bug fix (non-breaking change that fixes an issue)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update

Component(s) Affected

  • CLI (cmd/aicr, pkg/cli)
  • API server (cmd/aicrd, pkg/server)
  • Recipe engine / data (pkg/recipe)
  • Docs/examples (docs/, examples/)

Implementation Notes

  • Coverage post-condition (pkg/recipe/coverage.go): every stated dimension (service, accelerator, intent, os, platform — nodes is exempt/advisory) must appear with its exact value on ≥1 applied overlay (leaf or inheritance-chain ancestor). Runs on the FINAL applied set in both BuildRecipeResult and BuildRecipeResultWithEvaluator (after constraint exclusion and the mixin-failure fallback rebuild).
  • Actionable errors: minimal completion tuples per uncovered dimension — platform 'kubeflow' for criteria(service=eks, accelerator=h100, intent=training) requires os (valid: ubuntu) vs no recipe provides platform 'nim' for ... — plus a machine-readable details.uncovered array (dimension, requestedValue, validCompletions); evaluator path adds excludedOverlays/constraintWarnings context.
  • requireOSIfNeeded stays and runs first: per-dimension coverage does not subsume its joint service+accelerator OS gating (see recipe: joint-coverage formulation to subsume requireOSIfNeeded #1782). Pinned by TestBuildRecipeResult_GuardAndCoverageComposition.
  • Fail-closed evaluation: non-NotFound evaluator errors (malformed constraint path/expression → ErrCodeInvalidRequest, internal failures → ErrCodeInternal) now abort resolution with their codes preserved, in both per-overlay and mixin evaluation. ErrCodeNotFound ("value not found in snapshot") remains the designed graceful-exclusion signal, so recipe --snapshot degradation is unchanged and no new 404 surfaces on /v1/recipe.
  • Golden projection matrix (pkg/recipe/testdata/coverage_golden.yaml, 415 entries): pins the resolution outcome of every projection of every overlay's criteria. Any registry/overlay data change that flips an outcome fails TestCoverageGoldenMatrix; regenerate deliberately with AICR_UPDATE_GOLDEN=1 go test ./pkg/recipe/ -run TestCoverageGoldenMatrix.
  • Blast-radius fixes: 9 downstream test fixtures (pkg/client/v1, pkg/server, pkg/cli, chainsaw) queried silent partials; each individually triaged. One was genuinely wrong pre-branch (--service gke override onto an os=ubuntu base — no gke+ubuntu overlay exists).

Testing

make qualify        # full gate: test + lint + e2e (23/23 chainsaw) + scan — clean
make test-coverage  # 79.7% vs 75% floor — pass
make kwok-e2e RECIPE=eks-training   # 35/35 pods scheduled — pass

Coverage deltas vs origin/main: pkg/recipe: 87.3% → 88.0% (+0.7%); pkg/client/v1, pkg/server, pkg/cli unchanged. Every function in pkg/recipe/coverage.go exercised.

Live verification: the #1542 repro now exits non-zero with the completion-suggestion error; --os ubuntu and the generic-tier query (eks+h100+training, no platform) still exit 0.

Risk Assessment

  • Medium — Touches multiple components or has broader impact

Rollout notes: Intentional contract change: queries that previously returned 200 with silently-partial recipes now return 400 with details.uncovered; malformed constraints in recipe data now hard-fail resolution instead of silently excluding an overlay. No transition flag (pre-1.0, per approved design). External recipe-data consumers (internal recipes repo) notified on #1542. Revert is a clean single-commit revert.

Checklist

  • Tests pass locally (make test with -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) — GPG signing info

Recipe resolution now fails with ErrCodeInvalidRequest (HTTP 400) when a
query-stated criteria dimension (service, accelerator, intent, os,
platform) is not honored by any applied overlay, instead of silently
returning a partial recipe that echoes criteria it did not apply.

- New coverage post-condition runs on the final applied set of both
  BuildRecipeResult and BuildRecipeResultWithEvaluator (after constraint
  exclusion and the mixin-failure fallback rebuild).
- Errors carry actionable minimal completion tuples and a
  machine-readable details.uncovered context array.
- requireOSIfNeeded stays and runs first: per-dimension coverage does
  not subsume its joint service+accelerator OS gating.
- Constraint evaluation fails closed on non-NotFound evaluator errors
  (codes preserved); NotFound remains the designed graceful exclusion.
- nodes is exempt from coverage (advisory; no overlay gates on it).
- Golden projection matrix (415 entries) pins every projection outcome
  of the embedded overlay data against silent drift.

Fixes: #1542
Signed-off-by: Mark Chmarny <mark@chmarny.com>
@github-actions

Copy link
Copy Markdown
Contributor

@github-actions

Copy link
Copy Markdown
Contributor

Recipe evidence check

No leaf overlays affected by this PR.

This gate is warning-only and never blocks merge.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Added criteria-coverage validation for recipe resolution across service, accelerator, intent, OS, and platform dimensions. Resolution now reports structured invalid-request details for uncovered criteria, including completion suggestions and constraint-exclusion context. Constraint evaluation fails closed for non-NotFound errors. Tests, golden fixtures, CLI/API documentation, OpenAPI examples, scripts, and request fixtures were updated accordingly.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Suggested labels: theme/validation

Suggested reviewers: njhensley

🚥 Pre-merge checks | ✅ 4
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly matches the main change: enforcing stated-criteria coverage during recipe resolution.
Description check ✅ Passed The description is directly about the coverage-failure behavior and related evaluator changes in this PR.
Linked Issues check ✅ Passed The changes address #1542 by preventing silent partial resolutions when stated criteria are not covered, while preserving the OS guard.
Out of Scope Changes check ✅ Passed The extra docs, tests, and CLI snapshot adjustments all support the stated coverage fix and appear in scope.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/1542-criteria-coverage

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: 3

🤖 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 `@api/aicr/v1/server.yaml`:
- Around line 231-238: Update the GET and POST /v1/query 400-response
documentation to include the structured uncovered-dimension error contract
already described here: document details.uncovered with dimension,
requestedValue, and validCompletions, plus details.excludedOverlays and
details.constraintWarnings when snapshot-driven constraint exclusion occurs, and
add a representative example.

In `@pkg/cli/config_integration_test.go`:
- Around line 132-137: Update the integration test around root.Run to capture
the generated recipe output, then assert that its criteria contains service:
aks. Keep the existing --service override invocation and success assertion,
ensuring the test fails if testRecipeConfig’s default service: eks is used
instead.

In `@pkg/recipe/metadata_store.go`:
- Around line 766-770: Normalize evaluator failures in metadata_store.go at
lines 766-770 and 1167-1194 by passing each non-NotFound constraint error
through aicrerrors.PropagateOrWrap with ErrCodeInternal and the message “failed
to evaluate mixin constraint,” preserving existing structured codes and updating
the overlay comment’s guarantee. In metadata_store_test.go lines 305-339, add
coverage for a generic evaluator error asserting ErrCodeInternal while retaining
coded-error preservation coverage.
🪄 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: 3b0722ae-d947-47db-8ac1-e956e1078148

📥 Commits

Reviewing files that changed from the base of the PR and between 7e54fca and 465a3c4.

📒 Files selected for processing (15)
  • api/aicr/v1/server.yaml
  • docs/contributor/recipe.md
  • docs/user/api-reference.md
  • docs/user/cli-reference.md
  • pkg/cli/config_integration_test.go
  • pkg/client/v1/aicr_test.go
  • pkg/recipe/coverage.go
  • pkg/recipe/coverage_matrix_test.go
  • pkg/recipe/coverage_test.go
  • pkg/recipe/metadata_store.go
  • pkg/recipe/metadata_store_test.go
  • pkg/recipe/testdata/coverage_golden.yaml
  • pkg/server/recipe_handler_test.go
  • pkg/server/serve_test.go
  • tests/chainsaw/cli/config-file/chainsaw-test.yaml

Comment thread api/aicr/v1/server.yaml Outdated
Comment thread pkg/cli/config_integration_test.go Outdated
Comment thread pkg/recipe/metadata_store.go
@github-actions

github-actions Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Coverage Report ✅

Metric Value
Coverage 80.0%
Threshold 75%
Status Pass
Coverage Badge
![Coverage](https://img.shields.io/badge/coverage-80.0%25-brightgreen)

Merging this branch will increase overall coverage

Impacted Packages Coverage Δ 🤖
github.com/NVIDIA/aicr/pkg/cli 73.12% (+0.70%) 👍
github.com/NVIDIA/aicr/pkg/client/v1 76.84% (ø)
github.com/NVIDIA/aicr/pkg/constraints 97.94% (+10.31%) 🎉
github.com/NVIDIA/aicr/pkg/recipe 88.19% (+0.68%) 👍

Coverage by file

Changed files (no unit tests)

Changed File Coverage Δ Total Covered Missed 🤖
github.com/NVIDIA/aicr/pkg/cli/consts.go 0.00% (ø) 0 0 0
github.com/NVIDIA/aicr/pkg/cli/query.go 64.93% (+21.34%) 134 (+56) 87 (+53) 47 (+3) 🌟
github.com/NVIDIA/aicr/pkg/cli/recipe.go 88.72% (+4.42%) 133 (+12) 118 (+16) 15 (-4) 👍
github.com/NVIDIA/aicr/pkg/client/v1/aicr.go 76.18% (ø) 382 291 91
github.com/NVIDIA/aicr/pkg/constraints/evaluate.go 100.00% (+100.00%) 20 20 (+20) 0 (-20) 🌟
github.com/NVIDIA/aicr/pkg/recipe/coverage.go 96.38% (+96.38%) 138 (+138) 133 (+133) 5 (+5) 🌟
github.com/NVIDIA/aicr/pkg/recipe/metadata_store.go 87.61% (+0.73%) 436 (+9) 382 (+11) 54 (-2) 👍

Please note that the "Total", "Covered", and "Missed" counts above refer to code statements instead of lines of code. The value in brackets refers to the test coverage of that file in the old version of the code.

mchmarny and others added 5 commits July 16, 2026 06:05
ConstraintEvaluatorFunc returns a plain error; the fail-closed branches
in evaluateOverlayConstraints and evaluateMixinConstraints propagated it
as-is, so a non-StructuredError evaluator failure reached the server
layer as an uncoded 500. Route both through the existing
aicrerrors.PropagateOrWrap so an already-coded error still passes
through untouched, and anything else gets ErrCodeInternal.

Signed-off-by: Mark Chmarny <mark@chmarny.com>
fingerprint.ToCriteria only ever derives service/accelerator/os from a
snapshot (intent/platform always come back "any"), so an overlay tree
deliberately agnostic to one of those three dimensions could still trip
the criteria-coverage post-condition on a value the snapshot detected
but no recipe content distinguishes. buildRecipeFromCmdWithConfig now
retries once with any uncovered snapshot-derived dimensions cleared to
unstated, logging a warning per relaxed dimension; if any uncovered
dimension was user-stated (--config or a CLI flag), the error still
propagates unchanged. applyCriteriaFromConfig/applyCriteriaOverrides
gained a nil-safe touched-set parameter to track that distinction. This
is a caller-layer relaxation only — pkg/recipe stays strict. The retry
lives in the shared buildRecipeFromCmdWithConfig helper, so aicr query
and aicr mirror inherit it along with aicr recipe.

Also strengthens TestRecipeCmd_ConfigFlag_FlagOverride, which previously
only asserted the command succeeded (and would have passed even if
--service were silently ignored); it now reads the generated recipe
back and asserts the override actually landed.

Signed-off-by: Mark Chmarny <mark@chmarny.com>
The from-snapshot recipe/validate steps in tests/e2e/run.sh requested
--intent training without an explicit --service/--accelerator, relying
purely on fingerprint detection. On a GPU-less Kind cluster (fake
GPU operator faking device presence at the API level, no real driver
data), accelerator never gets derived, and Kind's training overlay
requires accelerator=h100 - a genuinely unsatisfiable combination,
since intent is user-stated and not eligible for the coverage
relaxation. Switch both invocations to --intent inference, which the
OS-agnostic kind-inference overlay covers on service=kind alone.

Signed-off-by: Mark Chmarny <mark@chmarny.com>
GET and POST /v1/query resolve recipes through the same engine as
/v1/recipe, so they surface the same criteria-coverage 400 contract
(details.uncovered). Mirror the description and examples onto both
verbs, and add a pointer to that contract in the api-reference.md query
section, which previously documented no error responses at all.

Also documents the CLI-layer relaxation for snapshot-derived
dimensions: cli-reference.md's aicr recipe snapshot section and
recipe.md's Criteria Coverage Post-Condition section now both call out
that the engine stays strict while the CLI's --snapshot path relaxes
derived-only coverage failures.

Signed-off-by: Mark Chmarny <mark@chmarny.com>
@mchmarny

Copy link
Copy Markdown
Member Author

Update (round 1 fixes pushed, be0f69aa..16392773):

The first CI run surfaced a real design-consequence failure worth calling out: on the nvkind GPU runners, the snapshot fingerprint detects os=ubuntu on Kind nodes, but the Kind overlay tree is deliberately OS-agnostic — strict coverage rejected the resolution (no recipe provides os 'ubuntu' ...) even though the OS-wildcard Kind recipe is the correct answer for that node. The committed golden matrix cannot catch this class: snapshot-derived values come from outside the catalog.

Amendment (maintainer-approved): the recipe engine stays strict, but the shared CLI snapshot path (recipe/query/mirror) now treats fingerprint-derived dimensions as advisory — when a coverage failure's uncovered dimensions are ALL snapshot-derived (not set by config or flags), they are relaxed with a warning and resolution retries once; any user-stated uncovered dimension still fails hard. pkg/client/v1.ResolveRecipeFromSnapshot takes caller-supplied criteria and derives nothing, so it is unchanged. Documented in docs/user/cli-reference.md and docs/contributor/recipe.md.

Also in this round: unstructured evaluator errors are now coded via PropagateOrWrap (CodeRabbit), /v1/query 400 docs mirror details.uncovered (CodeRabbit), the CLI override test asserts the override actually lands (CodeRabbit), and the e2e from-snapshot test uses a coverable intent on the GPU-less Kind cluster. make qualify clean.

The Kind overlay tree is deliberately OS-agnostic; --os ubuntu never
contributed content and is now correctly rejected by the criteria-coverage
post-condition as an uncovered stated dimension. Affects the runtime-install
GPU test action and tools/mirror-e2e.

Signed-off-by: Mark Chmarny <mark@chmarny.com>
@mchmarny
mchmarny requested a review from a team as a code owner July 16, 2026 13:28
@mchmarny

Copy link
Copy Markdown
Member Author

GPU test root cause (round 2, cd72d497): the failing H100 jobs pass --os ubuntu as an explicit CLI flag in .github/actions/runtime-install/generate-recipe.sh — not via snapshot detection, so the snapshot-relaxation correctly did not apply (user-stated dimensions are strict by design). The Kind overlay tree is deliberately OS-agnostic, so that flag never contributed content pre-branch (silent no-op) and is now correctly rejected as an uncovered stated dimension. Fixed by dropping --os ubuntu from the GPU test action and the same latent pattern in tools/mirror-e2e; verified locally by running the actual script for both failing CI shapes (inference+dynamo → 16 components, training → 12 components).

ayuskauskas
ayuskauskas previously approved these changes Jul 16, 2026

@ayuskauskas ayuskauskas left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approving. Faithful implementation of the #1542 dimension-uniform coverage post-condition, and retaining requireOSIfNeeded rather than subsuming it (as I'd claimed on the issue) is the correct call — coverage is satisfied when service/accelerator are honored by separate overlays, while the joint OS guard still needs to fire. Nicely pinned by the golden matrix.

One non-blocking follow-up on the CLI snapshot-relax path (pkg/cli/query.go):

uncoveredCoverageDimensions depends on (a) the coverage error being the first StructuredError in the chain and (b) Context["uncovered"] asserting to exactly []map[string]any. That holds today — the facade (aicr.go) and buildWithStore (builder.go) both return the builder's error unwrapped — but it's only unit-tested with a synthetic error (mkCoverageErr), and the e2e scripts were changed to drop --os ubuntu entirely, which sidesteps the exact scenario this path exists for. So relaxation is unexercised end-to-end, and a future re-wrap of the error (or a JSON boundary) would silently disable it with no failing test.

Suggestion: add an integration test through ResolveRecipeFromSnapshot that drives a real coverage failure → relax → retry, and/or make extraction robust by walking the chain for any StructuredError carrying uncovered instead of asserting on the first match.

coderabbitai[bot]

This comment was marked as resolved.

uncoveredCoverageDimensions previously asserted on the first
StructuredError in the chain (stderrors.As) and on the exact in-process
payload type []map[string]any, so an intermediate wrap between the
builder and the CLI — or a marshaling boundary — would have silently
disabled snapshot-derived criteria relaxation. Walk every
StructuredError node in the wrap chain and accept the decoded-JSON
payload shape ([]any of map[string]any) as well.

Pin the contract end-to-end: a new integration test drives a REAL
coverage failure through ResolveRecipeFromSnapshot on the embedded
catalog (kind + snapshot-detected ubuntu), asserts extraction sees the
builder's actual error shape, relaxes, and resolves on retry. Unit
tests cover wrapped (same/different code) and JSON-shaped payloads.

Follow-up to the PR #1784 review; related to #1542.

Signed-off-by: Mark Chmarny <mark@chmarny.com>
The merge of main brought in e0efbb5, which reverted the AKS H100
slinky-slurm recipe and deleted h100-aks-ubuntu-training-slurm.yaml.
The golden coverage matrix still pinned that overlay's aks+slurm
projections, failing TestCoverageGoldenMatrix in CI. Regenerated via
AICR_UPDATE_GOLDEN=1; the diff removes exactly the reverted overlay's
projection family.

Signed-off-by: Mark Chmarny <mark@chmarny.com>
@mchmarny

Copy link
Copy Markdown
Member Author

Both follow-ups addressed in 2715f3f:

  • uncoveredCoverageDimensions no longer stops at the first StructuredError: it walks every node in the wrap chain and identifies the coverage error by its own code + uncovered context, so an intermediate re-wrap between the builder and the CLI can't silently disable relaxation. It also accepts the decoded-JSON payload shape ([]any of map[string]any) alongside the in-process []map[string]any.
  • TestRelaxSnapshotDerivedCoverage_RealResolutionError drives the exact scenario the e2e scripts sidestepped: a snapshot fingerprinting to service=kind + os=ubuntu produces a real coverage failure from ResolveRecipeFromSnapshot against the embedded catalog, extraction is asserted against the builder's actual error shape, and the relaxed retry resolves. A future re-wrap or payload-shape drift now fails this test instead of silently disabling relaxation. Wrapped (same/different code) and JSON-shaped payloads are pinned in unit tests as well.

@njhensley njhensley 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.

📋 Multi-Persona Review

Method: 5 independent persona reviewers (Correctness, Domain/Architecture, API-contract, Test-coverage, CI-DX) → adversarial senior meta-reviewer that re-derived every finding from the resolved code at head 85eb09e. 10 candidates raised; 8 survived, 2 refuted.

Legend: 🔴 Blocker · 🟠 Major · 🟡 Minor · 🔵 Nitpick

Overall assessment

This is a well-engineered, well-tested bug fix with a sound core invariant. The coverage algorithm is correct: appliedOverlays genuinely includes inheritance-chain ancestors (so ancestor-supplied coverage counts) but excludes constraint-excluded overlays (so "honored only by an excluded overlay" fails loudly, as intended). The requireOSIfNeeded / per-dimension-coverage composition has no gap or double-gating. The shallow-copy retry in the CLI is safe (Criteria is all scalars). Error-code choice (400) is consistent with the sibling OS guard. Fail-closed evaluation aligns with the project's own anti-pattern rules.

No blockers. The one 🟠 is a test-coverage gap on the PR's central safety property, not a code defect. The 🟡s are accuracy/UX refinements.

Recommendation: Approve with comments — worth addressing the 🟠 (test the touched invariant) and the OpenAPI overpromise before or shortly after merge.

Confirmed non-issues (examined and cleared)

  • Coverage-set correctnessappliedOverlays correctly includes inheritance-chain ancestors but not excluded overlays; both verifyCriteriaCoverage call sites run on the final applied set; single-retry relaxation is provably sufficient.
  • next := *criteria aliasingCriteria is all scalar enum/int fields, no slices/pointers; the shallow copy fully isolates the retry.
  • touched keys ↔ coverage dimension names — identical constants in consts.go and coverage.go; nodes intentionally absent from both.
  • No internal leakverifyCriteriaCoverage uses NewWithContext (no Cause), so WriteErrorFromErr's 4xx details.error branch never fires; message is only completion clauses. Struct↔JSON tags align.
  • Guard compositionrequireOSIfNeeded (joint service+accel) and per-dimension coverage neither subsume each other nor double-gate.
  • PropagateOrWrap / wrap-chain walk — no code-clobbering; uncoveredCoverageDimensions inspects every chain node and won't false-match a bare ErrCodeInvalidRequest.
  • Operational call sites — the 4 changed shell/chainsaw files adapt correctly (drop stated-but-undistinguished --os, retarget gkeaks); no || true masking; no unmodified in-repo resolver call site breaks post-merge; no shell-injection.
  • Blast-radius fixtures — spot-checked; none weaken assertions (config_integration_test was strengthened).
  • gpu-alias gb200-on-gke doc example returns a franken-reciperefuted: no gb200+gke overlay exists, so service/os are uncovered → this actually returns 400, not a 200 partial. (Minor residue: the api-reference.md:146 example's comment implies os=cos makes it valid when it would 400 on service — worth a re-check of the intended illustration.)
  • e2e --intent training→inference narrows coverage for no reasonrefuted: the flip is almost certainly coverage-forced (mirroring the sibling --os drops); the only residue is that run.sh lacks the explanatory comment its siblings got.

Summary

🔴 Blocker 0 | 🟠 Major 1 | 🟡 Minor 5 | 🔵 Nitpick 2     Recommendation: Approve with comments
Tier Finding Location
🟠 touched-map invariant untested (silent-relax regression could ship green) pkg/cli/recipe.go:374
🟡 Fail-closed aborts on unparseable snapshot value (snapshot path only) pkg/recipe/metadata_store.go:1195
🟡 Misleading "no recipe provides" when sole overlay was excluded pkg/recipe/coverage.go:248
🟡 OpenAPI/docs promise HTTP fields that can't be emitted api/aicr/v1/server.yaml:237
🟡 Snapshot-relaxation policy CLI-only, undocumented SDK asymmetry pkg/cli/query.go:237
🔵 Golden matrix doesn't guard completion-suggestion content pkg/recipe/coverage_matrix_test.go:32
🔵 Completions constraint-unaware + multi-element wording unasserted pkg/recipe/coverage.go:88

Generated via multi-persona review + adversarial meta-review. Findings independently re-derived from resolved code; refuted candidates listed above for transparency.

Comment thread pkg/cli/recipe.go
Comment thread pkg/recipe/metadata_store.go
Comment thread pkg/recipe/coverage.go
Comment thread api/aicr/v1/server.yaml Outdated
Comment thread pkg/cli/query.go
Comment thread pkg/recipe/coverage_matrix_test.go
Comment thread pkg/recipe/coverage.go
mchmarny and others added 2 commits July 20, 2026 09:25
- pkg/cli: pin the touched-map invariant — table tests assert
  applyCriteriaOverrides/applyCriteriaFromConfig mark exactly the stated
  coverage dimensions, plus end-to-end CLI cases proving a user-stated
  uncovered dimension propagates the coverage error while a
  fingerprint-derived one is relaxed
- pkg/constraints: route parsed.Evaluate errors through PropagateOrWrap so
  the version parser's ErrCodeInvalidRequest is not clobbered to
  ErrCodeInternal; add TestEvaluate pinning the eval error-code contract
- pkg/recipe: truthful wording when an uncovered dimension is provided only
  by constraint-excluded overlays (was: 'no recipe provides'); golden matrix
  now pins per-dimension validCompletions content; assert multi-tuple and
  multi-value completion wording
- api/docs: scope excludedOverlays/constraintWarnings to snapshot-driven
  (CLI/SDK) resolution in all four HTTP 400 descriptions and api-reference;
  document strict facade coverage + CLI-only relaxation on
  ResolveRecipeFromSnapshot
- tests/e2e: explain why kind queries use --intent inference
- golden regenerated (also folds in main's new h100-aks-ubuntu-training-slurm
  projections missing since the merge)

Signed-off-by: Mark Chmarny <mark@chmarny.com>
@mchmarny

Copy link
Copy Markdown
Member Author

Multi-persona review round addressed in 760cf2a — all 7 inline threads replied to and resolved (1 🟠, 4 🟡, 2 🔵; details in each thread). Notes on the review-body residues that had no inline thread:

  • api-reference.md:146 example (gpu=gb200&service=gke&os=cos) — verified live: it resolves with os=cos (per-dimension coverage: gb200-any covers the accelerator, gke-cos covers service+os) and fails without os with specify an OS (valid: cos). The example and its comment are accurate as written; no change.
  • e2e run.sh intent flip — confirmed coverage-forced (kind-inference is the only accelerator-less kind intent overlay; training exists only h100-qualified, and the GPU-less kind cluster can't state an accelerator). Added the explanatory comment both siblings got.
  • Golden freshness — regenerating for ValidCompletions also surfaced that the main merge (254a91f) brought h100-aks-ubuntu-training-slurm without its golden projections (TestCoverageGoldenMatrix failed at that commit); the regenerated golden folds those in.

Gates: make qualify clean (one tests/releasepolicy deadline flake under parallel load; passes in isolation and on the clean rerun), make test-coverage 80.0% vs 75% floor. Coverage deltas vs pre-round HEAD: pkg/constraints 87.6% → 97.9%, pkg/cli 72.3% → 73.1%, pkg/recipe 88.1% → 88.2%, pkg/client/v1 unchanged (doc-only).

@mchmarny
mchmarny enabled auto-merge (squash) July 20, 2026 18:03

@njhensley njhensley 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.

Re-reviewed at 760cf2a3 — all 7 findings from the multi-persona round are addressed, and every new test executes and passes locally (pkg/constraints, pkg/recipe, pkg/cli green).

  • 🟠 touched-map invarianttouched_invariant_test.go pins production (exact-set table tests over all 5 dimensions) and end-to-end (stated --os propagates the coverage error; derived os relaxes and drops from output). A dropped/mis-keyed mark now fails four tests.
  • 🟡 fail-closed on unparseable value — kept fail-closed (correct); fixed the real sub-defect via PropagateOrWrap in evaluate.go so ErrCodeInvalidRequest reaches the engine un-clobbered, pinned by TestEvaluate.
  • 🟡 misleading "no recipe provides"excludedOverlayProvides + onlyExcluded wording, with a subtest asserting the old wording is gone.
  • 🟡 OpenAPI overpromise — all four HTTP 400 descriptions + api-reference now scope excludedOverlays/constraintWarnings to snapshot-only.
  • 🟡 SDK parity — documented the STRICT post-condition on ResolveRecipeFromSnapshot (right call; the facade has no touched-provenance).
  • 🔵 golden completion contentValidCompletions added to the classification and diffed; golden regenerated (also folded in the missing h100-aks-ubuntu-training-slurm projections).
  • 🔵 multi-element wording — verbatim multi-tuple and sorted multi-value assertions added; deprioritizing excluded completions declined with sound reasoning.

The two declined items are defensible design decisions, not gaps. Approving.

@mchmarny
mchmarny merged commit 544491c into main Jul 20, 2026
168 checks passed
@mchmarny
mchmarny deleted the fix/1542-criteria-coverage branch July 20, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/api area/ci area/cli area/docs area/tests size/XL theme/recipes Recipe expansion, overlays, mixins, and component registry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(recipe): OS-required guard ignores platform/intent, allowing partial recipe resolution

3 participants