validate: fail-closed GKE device-plugin ownership check - #2000
Conversation
Recipe evidence checkProtected recipesRecipes with committed evidence (
Other affected recipes without evidence yet: 10These recipes are affected by this PR but carry no committed evidence pointer, so there is
How to refresh evidenceRun on a cluster matching the recipe's aicr snapshot -o snapshot.yaml
# Profiled families (AKS gpuStack): capture the pool projection and
# hydrate the recipe with the pointer's recorded 'profile:' selection
# first — validating the raw overlay resolves only the declaration
# default, and 'aicr validate' has no --profile flag:
# az aks nodepool list -g <rg> --cluster-name <cluster> -o json > pools.json
# aicr snapshot --aks-gpu-pools pools.json -o snapshot.yaml
# aicr recipe -s snapshot.yaml --intent <intent> [--platform <platform>] \
# --profile <name>=<value> -o recipe.yaml
# State the target leaf's intent/platform explicitly (the snapshot
# fingerprint supplies service/accelerator/OS but intent and platform
# default to 'any') and pass -r recipe.yaml below instead of the raw
# overlay.
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>.yamlThis gate is warning-only and never blocks merge. See ADR-007 for the trust model. |
|
Note Reviews pausedIt 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 Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughAdds the Estimated code review effort: 4 (Complex) | ~45 minutes Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
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 `@pkg/validator/validator_test.go`:
- Around line 355-410: Extend
TestCheckReadinessEvaluatesReadinessPhaseConstraints with a recipe containing
both a top-level Validation.Constraints entry and a
Validation.Readiness.Constraints entry; make one or both fail to verify both are
evaluated, confirm the resulting error includes the relevant remediation, and
snapshot validationInput.Constraints before checkReadiness to assert it remains
unchanged afterward, preserving the combined ordering and non-aliasing behavior.
🪄 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: 508c292a-df5b-4790-8e79-48253edb795d
📒 Files selected for processing (10)
docs/integrator/recipe-development.mddocs/user/validation.mdpkg/constraints/doc.gopkg/constraints/evaluate.gopkg/constraints/gpu_nodes.gopkg/constraints/gpu_nodes_test.gopkg/validator/validator.gopkg/validator/validator_test.gorecipes/overlays/gke-cos.yamltests/uat/gcp/cluster-config.yaml
7bac947 to
f6cab51
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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 `@pkg/collector/topology/topology_test.go`:
- Around line 465-481: Refactor TestIsTruncatedNodeListRoundTrip into a
table-driven test containing the truncated, full, and exact-limit cases with
their expected results. Iterate over the cases using subtests, while preserving
the existing formatNodeList inputs and IsTruncatedNodeList assertions.
In `@pkg/constraints/gpu_nodes.go`:
- Line 22: Replace the deprecated validation import and validator calls in the
GPU node constraint logic with content.IsQualifiedName and content.IsLabelValue.
Remove the k8s.io/apimachinery/pkg/util/validation dependency while preserving
the existing validation behavior.
🪄 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: 7863614d-d4f6-425b-b95e-81ca870595f2
📒 Files selected for processing (14)
docs/contributor/validator.mddocs/integrator/recipe-development.mddocs/user/validation.mdpkg/collector/topology/topology.gopkg/collector/topology/topology_test.gopkg/constraints/doc.gopkg/constraints/evaluate.gopkg/constraints/gpu_nodes.gopkg/constraints/gpu_nodes_test.gopkg/validator/doc.gopkg/validator/validator.gopkg/validator/validator_test.gorecipes/overlays/gke-cos.yamltests/uat/gcp/cluster-config.yaml
f6cab51 to
a663fdd
Compare
There was a problem hiding this comment.
Actionable comments posted: 4
🤖 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 `@pkg/constraints/gpu_nodes_test.go`:
- Around line 397-409: Refactor TestSummarizeNodesCapsList into a table-driven
test containing both the capped seven-node case and the uncapped single-node
case, then iterate through the table with named subtests while preserving the
existing expected outputs.
In `@pkg/constraints/gpu_nodes.go`:
- Around line 204-208: Update the topology label decoding around
cutLabelEncoding so readings missing the separator, with an empty node list, or
containing empty node members are rejected as malformed validation data.
Propagate the decoding error through evaluateNoGPUNodeHasKey so both positive
and negated checks fail closed instead of producing an empty labelNodeSet. Add
regression tests covering positive and negated evaluations for each malformed
form.
- Around line 134-140: Update the node-set constraint parsing validation around
strings.Cut to remove the want == "" rejection, allowing key= values to be
validated by validation.IsValidLabelValue. Add a test case covering a valid
empty label value and preserve rejection of malformed inputs through the
existing validation flow.
In `@pkg/validator/validator.go`:
- Around line 80-82: Update the remediation handling in the validator
error-message construction to trim c.Remediation before checking whether it is
non-empty, then append the trimmed value only when it contains content. Preserve
the existing newline formatting for valid remediation text, and add a test
covering whitespace-only Remediation to ensure it does not append an extra
newline.
🪄 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: de16349f-e220-4240-ab15-89216983aa1d
📒 Files selected for processing (15)
docs/contributor/validator.mddocs/integrator/recipe-development.mddocs/user/validation.mdpkg/collector/topology/topology.gopkg/collector/topology/topology_test.gopkg/constraints/doc.gopkg/constraints/evaluate.gopkg/constraints/extractor.gopkg/constraints/gpu_nodes.gopkg/constraints/gpu_nodes_test.gopkg/validator/doc.gopkg/validator/validator.gopkg/validator/validator_test.gorecipes/overlays/gke-cos.yamltests/uat/gcp/cluster-config.yaml
a663fdd to
26e0748
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 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/contributor/validator.md`:
- Around line 47-53: Update the later pre-flight section describing
checkReadiness to state that it evaluates both top-level validation.constraints
and validation.readiness.constraints, matching the contract documented near the
phase checks. Remove any wording that limits pre-flight evaluation to top-level
constraints only.
In `@pkg/validator/validator_test.go`:
- Around line 416-439: Update the aliasing coverage in the checkReadiness test
around the topLevel setup and post-check assertions: construct topLevel with
spare capacity and initialize its unused tail with a sentinel value, then verify
after checkReadiness that both its length and unused tail remain unchanged. Keep
the existing error assertions and ensure the test would fail if checkReadiness
appends readiness constraints into the aliased backing array.
🪄 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: a168baec-79c7-4640-a371-4a6cb526e2a1
📒 Files selected for processing (15)
docs/contributor/validator.mddocs/integrator/recipe-development.mddocs/user/validation.mdpkg/collector/topology/topology.gopkg/collector/topology/topology_test.gopkg/constraints/doc.gopkg/constraints/evaluate.gopkg/constraints/extractor.gopkg/constraints/gpu_nodes.gopkg/constraints/gpu_nodes_test.gopkg/validator/doc.gopkg/validator/validator.gopkg/validator/validator_test.gorecipes/overlays/gke-cos.yamltests/uat/gcp/cluster-config.yaml
91c0d71 to
1aede4d
Compare
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values. operator (the default) keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser and carries the positive NVIDIA#1755 node-set constraint (gke-no-default-nvidia-gpu-device-plugin=true on every GPU node); csp-managed declares advertiser: external, disables the operator's plugin, and carries the negated constraint — making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: component-catalog (profile-led GKE ownership section; driver auto-detect subordination now stated as path-ownership-based), validation, cli-reference, api-reference (GKE /v1 cut-over), recipe-development, data-extension, contributor/recipe. GCP UAT asserts pin v1alpha3 + selectedProfile. Live-cluster qualification of both GKE values and v2 evidence signing follow before un-draft. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
…1755 gate UAT GCP cluster provisioning has been broken since the NVIDIA#1755 readiness gate and its cluster prerequisite landed (NVIDIA#1998 docs, NVIDIA#2000 enforcement + the labeled UAT pool): on GKE, the managed driver install (gpu-driver-version=default) is FINALIZED by an init container (nvidia-driver-installer) of the same kube-system DaemonSet the gke-no-default-nvidia-gpu-device-plugin=true label disables — the DaemonSet's node affinity is NotIn ["true"]. A freshly provisioned labeled pool therefore comes up with the driver staged on disk but never installed: no kernel module, no /dev/nvidia*, zero allocatable GPUs, and the GPU Operator's toolkit loops on driver-validation forever. Proven on run 30930940652 (the only config change since the last green run, v0.18.0/30111681059, is the label). The label's documented contract is device-plugin ownership only; its GKE implementation excludes the whole managed DaemonSet, driver installer included. The prerequisite the gate enforced — label + GKE-managed driver — is therefore unsatisfiable on a fresh pool. Nothing can pass it. Interim fix, until the ADR-015 gpuStack profile lands (NVIDIA#1761 rollout PR 3, NVIDIA#2044): - tests/uat/gcp/cluster-config.yaml: drop the pool label — GKE-default provisioning (managed driver + managed plugin), the shape every green run actually used. - recipes/overlays/gke-cos.yaml: withdraw the readiness constraint; it fails closed on the unlabeled cluster above. The NVIDIA#1755 node-set constraint FORM (pkg/constraints) is untouched and remains available. - docs/user/component-catalog.md, docs/user/validation.md: correct the claim that the label does not affect driver provisioning, document the DaemonSet bundling and the standalone nvidia-driver-installer pairing, and mark enforcement as temporarily withdrawn. Known, accepted interim consequence: GKE-default pools run BOTH GKE's plugin and the operator's plugin as nvidia.com/gpu advertisers — the documented latent NVIDIA#1755 conflict every pre-NVIDIA#2000 green run also carried. The gpuStack profile re-lands enforcement symmetrically and coherently: csp-managed (default) requires the label absent with GKE's plugin as sole advertiser (advertiser: external); operator requires the label plus a label-surviving driver path. Refs NVIDIA#1755, NVIDIA#1761, NVIDIA#2000 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
…1755 gate UAT GCP cluster provisioning has been broken since the NVIDIA#1755 readiness gate and its cluster prerequisite landed (NVIDIA#1998 docs, NVIDIA#2000 enforcement + the labeled UAT pool): on GKE, the managed driver install (gpu-driver-version=default) is FINALIZED by an init container (nvidia-driver-installer) of the same kube-system DaemonSet the gke-no-default-nvidia-gpu-device-plugin=true label disables — the DaemonSet's node affinity is NotIn ["true"]. A freshly provisioned labeled pool therefore comes up with the driver staged on disk but never installed: no kernel module, no /dev/nvidia*, zero allocatable GPUs, and the GPU Operator's toolkit loops on driver-validation forever. Proven on run 30930940652 (the only config change since the last green run, v0.18.0/30111681059, is the label). The label's documented contract is device-plugin ownership only; its GKE implementation excludes the whole managed DaemonSet, driver installer included. The prerequisite the gate enforced — label + GKE-managed driver — is therefore unsatisfiable on a fresh pool. Nothing can pass it. Interim fix, until the ADR-015 gpuStack profile lands (NVIDIA#1761 rollout PR 3, NVIDIA#2044): - tests/uat/gcp/cluster-config.yaml: drop the pool label — GKE-default provisioning (managed driver + managed plugin), the shape every green run actually used. - recipes/overlays/gke-cos.yaml: withdraw the readiness constraint; it fails closed on the unlabeled cluster above. The NVIDIA#1755 node-set constraint FORM (pkg/constraints) is untouched and remains available. - docs/user/component-catalog.md, docs/user/validation.md: correct the claim that the label does not affect driver provisioning, document the DaemonSet bundling and the standalone nvidia-driver-installer pairing, and mark enforcement as temporarily withdrawn. Known, accepted interim consequence: GKE-default pools run BOTH GKE's plugin and the operator's plugin as nvidia.com/gpu advertisers — the documented latent NVIDIA#1755 conflict every pre-NVIDIA#2000 green run also carried. The gpuStack profile re-lands enforcement symmetrically and coherently: csp-managed (default) requires the label absent with GKE's plugin as sole advertiser (advertiser: external); operator requires the label plus a label-surviving driver path. Refs NVIDIA#1755, NVIDIA#1761, NVIDIA#2000 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
…1755 gate UAT GCP cluster provisioning has been broken since the NVIDIA#1755 readiness gate and its cluster prerequisite landed (NVIDIA#1998 docs, NVIDIA#2000 enforcement + the labeled UAT pool): on GKE, the managed driver install (gpu-driver-version=default) is FINALIZED by an init container (nvidia-driver-installer) of the same kube-system DaemonSet the gke-no-default-nvidia-gpu-device-plugin=true label disables — the DaemonSet's node affinity is NotIn ["true"]. A freshly provisioned labeled pool therefore comes up with the driver staged on disk but never installed: no kernel module, no /dev/nvidia*, zero allocatable GPUs, and the GPU Operator's toolkit loops on driver-validation forever. Proven on run 30930940652 (the only config change since the last green run, v0.18.0/30111681059, is the label). The label's documented contract is device-plugin ownership only; its GKE implementation excludes the whole managed DaemonSet, driver installer included. The prerequisite the gate enforced — label + GKE-managed driver — is therefore unsatisfiable on a fresh pool. Nothing can pass it. Interim fix, until the ADR-015 gpuStack profile lands (NVIDIA#1761 rollout PR 3, NVIDIA#2044): - tests/uat/gcp/cluster-config.yaml: drop the pool label — GKE-default provisioning (managed driver + managed plugin), the shape every green run actually used. - recipes/overlays/gke-cos.yaml: withdraw the readiness constraint; it fails closed on the unlabeled cluster above. The NVIDIA#1755 node-set constraint FORM (pkg/constraints) is untouched and remains available. - docs/user/component-catalog.md, docs/user/validation.md: correct the claim that the label does not affect driver provisioning, document the DaemonSet bundling and the standalone nvidia-driver-installer pairing, and mark enforcement as temporarily withdrawn. Known, accepted interim consequence: GKE-default pools run BOTH GKE's plugin and the operator's plugin as nvidia.com/gpu advertisers — the documented latent NVIDIA#1755 conflict every pre-NVIDIA#2000 green run also carried. The gpuStack profile re-lands enforcement symmetrically and coherently: csp-managed (default) requires the label absent with GKE's plugin as sole advertiser (advertiser: external); operator requires the label plus a label-surviving driver path. Refs NVIDIA#1755, NVIDIA#1761, NVIDIA#2000 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: component-catalog (profile-led GKE ownership section; driver auto-detect subordination now stated as path-ownership-based), validation, cli-reference, api-reference (GKE /v1 cut-over), recipe-development, data-extension, contributor/recipe. GCP UAT asserts pin v1alpha3 + selectedProfile. Live-cluster qualification of both GKE values and v2 evidence signing follow before un-draft. Default flip + review round: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. This round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. This round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. This round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. This round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. This round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), and the attestation package godoc covers the v2 predicate. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), and the attestation package godoc covers the v2 predicate. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), and the attestation package godoc covers the v2 predicate. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), and the attestation package godoc covers the v2 predicate. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), and the attestation package godoc covers the v2 predicate. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, so the cut-over invalidates nothing in-repo. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), and the attestation package godoc covers the v2 predicate. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, and none is rejected by the new checkpoints; the two committed GKE pointers (h100-gke-cos-training) are v1 evidence over the now-profiled recipe, so the cut-over demotes them to historical — recipe-health Evidence links move to pending until the planned per-value v2 re-signing. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), the attestation package godoc covers the v2 predicate, and the gke-gpu-setup retrofit procedure now switches the pool's driver mode to gpu-driver-version=disabled (node-pools update --accelerator) before relying on the standalone installer — Google's nvidia-driver-installer DaemonSet ignores automatic-install pools, so deploying it over a gpu-driver-version=default pool left replacement nodes driverless. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, and none is rejected by the new checkpoints; the two committed GKE pointers (h100-gke-cos-training) are v1 evidence over the now-profiled recipe, so the cut-over demotes them to historical — recipe-health Evidence links move to pending until the planned per-value v2 re-signing. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), the attestation package godoc covers the v2 predicate, and the gke-gpu-setup retrofit procedure now switches the pool's driver mode to gpu-driver-version=disabled (node-pools update --accelerator) before relying on the standalone installer — Google's nvidia-driver-installer DaemonSet ignores automatic-install pools, so deploying it over a gpu-driver-version=default pool left replacement nodes driverless. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, and none is rejected by the new checkpoints; the two committed GKE pointers (h100-gke-cos-training) are v1 evidence over the now-profiled recipe, so the cut-over demotes them to historical — recipe-health Evidence links move to pending until the planned per-value v2 re-signing. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), the attestation package godoc covers the v2 predicate, and the gke-gpu-setup retrofit procedure now switches the pool's driver mode to gpu-driver-version=disabled (node-pools update --accelerator) before relying on the standalone installer — Google's nvidia-driver-installer DaemonSet ignores automatic-install pools, so deploying it over a gpu-driver-version=default pool left replacement nodes driverless. Live-qualification round: gcp-managed qualification on a GKE H100 cluster came back deployment 4/4 and conformance 11/11 green (NCCL all-reduce 338 GB/s; secure-accelerator-access passed both intents), but every inference-perf vLLM worker crash-looped with "Failed to infer device type" — GKE's managed device plugin injects the /usr/local/nvidia driver mount through its device-plugin Allocate response without setting LD_LIBRARY_PATH, so libcuda.so.1 is invisible to the dynamic loader (A/B on the crashing worker: LD_LIBRARY_PATH=/usr/local/nvidia/lib64 alone flips torch CUDA to available). Both Dynamo deployment templates now wrap the worker command in a shell that appends /usr/local/nvidia/lib64 to LD_LIBRARY_PATH with the ${VAR:+} guard — a pod-level env override would clobber the image's own LD_LIBRARY_PATH (nixl/ucx/cuda entries), and the append is a no-op under the operator toolkit flow, so no platform branching is needed (pinned by TestParseDynamoTemplate_WorkerDriverLibPathAppend). Review round: the GKE retrofit runbook reorders to driver-path-first/label-last — installer, driver-mode switch, verification, then the label handoff — because the label-first order left autoscaled/repaired nodes driverless until the driver-mode switch, and it documents rollback (remove the label); the evidence-refresh doc makes --platform conditional on the target leaf's criteria and records that legacy v1 pointers carry no profile field (a v1->v2 conversion selects the profile from the target cluster, not the pointer); the allocation-policy resolver suppresses the warn-and-prefer diagnostic under a declared external advertiser, whose branch aggregates both operator components; the data-extension heading drops its stale "(AKS example)" suffix (no inbound anchor links); a pointer_test comment references the real coherence matrix (TestValidateBundleProfileCoherence). Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, and none is rejected by the new checkpoints; the two committed GKE pointers (h100-gke-cos-training) are v1 evidence over the now-profiled recipe, so the cut-over demotes them to historical — recipe-health Evidence links move to pending until the planned per-value v2 re-signing. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), the attestation package godoc covers the v2 predicate, and the gke-gpu-setup retrofit procedure now switches the pool's driver mode to gpu-driver-version=disabled (node-pools update --accelerator) before relying on the standalone installer — Google's nvidia-driver-installer DaemonSet ignores automatic-install pools, so deploying it over a gpu-driver-version=default pool left replacement nodes driverless. Live-qualification round: gcp-managed qualification on a GKE H100 cluster came back deployment 4/4 and conformance 11/11 green (NCCL all-reduce 338 GB/s; secure-accelerator-access passed both intents), but every inference-perf vLLM worker crash-looped with "Failed to infer device type" — GKE's managed device plugin injects the /usr/local/nvidia driver mount through its device-plugin Allocate response without setting LD_LIBRARY_PATH, so libcuda.so.1 is invisible to the dynamic loader (A/B on the crashing worker: LD_LIBRARY_PATH=/usr/local/nvidia/lib64 alone flips torch CUDA to available). Both Dynamo deployment templates now wrap the worker command in a shell that appends /usr/local/nvidia/lib64 to LD_LIBRARY_PATH with the ${VAR:+} guard — a pod-level env override would clobber the image's own LD_LIBRARY_PATH (nixl/ucx/cuda entries), and the append is a no-op under the operator toolkit flow, so no platform branching is needed (pinned by TestParseDynamoTemplate_WorkerDriverLibPathAppend). Review round: the GKE retrofit runbook reorders to driver-path-first/label-last — installer, driver-mode switch, verification, then the label handoff — because the label-first order left autoscaled/repaired nodes driverless until the driver-mode switch, and it documents rollback (remove the label); the evidence-refresh doc makes --platform conditional on the target leaf's criteria and records that legacy v1 pointers carry no profile field (a v1->v2 conversion selects the profile from the target cluster, not the pointer); the allocation-policy resolver suppresses the warn-and-prefer diagnostic under a declared external advertiser, whose branch aggregates both operator components; the data-extension heading drops its stale "(AKS example)" suffix (no inbound anchor links); a pointer_test comment references the real coherence matrix (TestValidateBundleProfileCoherence). Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, and none is rejected by the new checkpoints; the two committed GKE pointers (h100-gke-cos-training) are v1 evidence over the now-profiled recipe, so the cut-over demotes them to historical — recipe-health Evidence links move to pending until the planned per-value v2 re-signing. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), the attestation package godoc covers the v2 predicate, and the gke-gpu-setup retrofit procedure now switches the pool's driver mode to gpu-driver-version=disabled (node-pools update --accelerator) before relying on the standalone installer — Google's nvidia-driver-installer DaemonSet ignores automatic-install pools, so deploying it over a gpu-driver-version=default pool left replacement nodes driverless. Live-qualification round: gcp-managed qualification on a GKE H100 cluster came back deployment 4/4 and conformance 11/11 green (NCCL all-reduce 338 GB/s; secure-accelerator-access passed both intents), but every inference-perf vLLM worker crash-looped with "Failed to infer device type" — GKE's managed device plugin injects the /usr/local/nvidia driver mount through its device-plugin Allocate response without setting LD_LIBRARY_PATH, so libcuda.so.1 is invisible to the dynamic loader (A/B on the crashing worker: LD_LIBRARY_PATH=/usr/local/nvidia/lib64 alone flips torch CUDA to available). Both Dynamo deployment templates now wrap the worker command in a shell that appends /usr/local/nvidia/lib64 to LD_LIBRARY_PATH with the ${VAR:+} guard — a pod-level env override would clobber the image's own LD_LIBRARY_PATH (nixl/ucx/cuda entries), and the append is a no-op under the operator toolkit flow, so no platform branching is needed (pinned by TestParseDynamoTemplate_WorkerDriverLibPathAppend). Review round: the GKE retrofit runbook reorders to driver-path-first/label-last — installer, driver-mode switch, verification, then the label handoff — because the label-first order left autoscaled/repaired nodes driverless until the driver-mode switch, and it documents rollback (remove the label); the evidence-refresh doc makes --platform conditional on the target leaf's criteria and records that legacy v1 pointers carry no profile field (a v1->v2 conversion selects the profile from the target cluster, not the pointer); the allocation-policy resolver suppresses the warn-and-prefer diagnostic under a declared external advertiser, whose branch aggregates both operator components; the data-extension heading drops its stale "(AKS example)" suffix (no inbound anchor links); a pointer_test comment references the real coherence matrix (TestValidateBundleProfileCoherence). Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules (external + devicePlugin.enabled=true and external + DRA gpus.enabled=true reject). Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, and none is rejected by the new checkpoints; the two committed GKE pointers (h100-gke-cos-training) are v1 evidence over the now-profiled recipe, so the cut-over demotes them to historical — recipe-health Evidence links move to pending until the planned per-value v2 re-signing. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), the attestation package godoc covers the v2 predicate, and the gke-gpu-setup retrofit procedure now switches the pool's driver mode to gpu-driver-version=disabled (node-pools update --accelerator) before relying on the standalone installer — Google's nvidia-driver-installer DaemonSet ignores automatic-install pools, so deploying it over a gpu-driver-version=default pool left replacement nodes driverless. Live-qualification round: gcp-managed qualification on a GKE H100 cluster came back deployment 4/4 and conformance 11/11 green (NCCL all-reduce 338 GB/s; secure-accelerator-access passed both intents), but every inference-perf vLLM worker crash-looped with "Failed to infer device type" — GKE's managed device plugin injects the /usr/local/nvidia driver mount through its device-plugin Allocate response without setting LD_LIBRARY_PATH, so libcuda.so.1 is invisible to the dynamic loader (A/B on the crashing worker: LD_LIBRARY_PATH=/usr/local/nvidia/lib64 alone flips torch CUDA to available). Both Dynamo deployment templates now wrap the worker command in a shell that appends /usr/local/nvidia/lib64 to LD_LIBRARY_PATH with the ${VAR:+} guard — a pod-level env override would clobber the image's own LD_LIBRARY_PATH (nixl/ucx/cuda entries), and the append is a no-op under the operator toolkit flow, so no platform branching is needed (pinned by TestParseDynamoTemplate_WorkerDriverLibPathAppend). Review round: the GKE retrofit runbook reorders to driver-path-first/label-last — installer, driver-mode switch, verification, then the label handoff — because the label-first order left autoscaled/repaired nodes driverless until the driver-mode switch, and it documents rollback (remove the label); the evidence-refresh doc makes --platform conditional on the target leaf's criteria and records that legacy v1 pointers carry no profile field (a v1->v2 conversion selects the profile from the target cluster, not the pointer); the allocation-policy resolver suppresses the warn-and-prefer diagnostic under a declared external advertiser, whose branch aggregates both operator components; the data-extension heading drops its stale "(AKS example)" suffix (no inbound anchor links); a pointer_test comment references the real coherence matrix (TestValidateBundleProfileCoherence). Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Land the GKE adopter and the profile extensions ADR-015 staged for it (issue NVIDIA#1761 rollout PR 3), completing the three-PR series after the generic core (NVIDIA#1933) and the AKS adopter (NVIDIA#1967). GKE conversion: recipes/overlays/gke-cos.yaml moves to aicr.run/v1alpha3 and declares gpuStack with two values, named after the AKS convention. gcp-managed (the default) declares advertiser: external, disables the operator's plugin, and carries the negated NVIDIA#1755 node-set constraint (no GPU node may carry gke-no-default-nvidia-gpu-device-plugin) — the default GKE cluster shape, making the previously unsupported "GKE's managed plugin owns nvidia.com/gpu" state declarable and verifiable; operator-managed keeps the GPU Operator's device plugin as the sole nvidia.com/gpu advertiser with the positive constraint. The overlay's readiness constraint from NVIDIA#2000 relocates into the profile values, as forecast when it landed; every GKE-COS leaf inherits the declaration unchanged. operator-selfdriver and the NVIDIA#1716 installer stay deferred on DD5's durable signal. Canonical NVIDIA#1327 descriptor: the policy path vocabulary that existed as three private copies (pkg/bundler, pkg/validator/v1, pkg/recipe) moves to the new dependency-neutral pkg/allocpolicy — one descriptor and one tuple-coherence evaluator, consumed by profile validation, the hydrating artifact gate, bundler enforcement, and validation-time policy resolution. The descriptor is append-only; its deterministic Identity() feeds evidence currentness. advertiser "external" activates: the core's three deferral rejections are replaced with descriptor-backed validation. A profile that owns advertisement — a declared external advertiser or explicit ownership of a non-synthetic selector path — triggers the recomputed closure: RecipeResult.EffectiveLockSet() joins every enabled descriptor component's selector paths (plus synthetic presence) to the declared ownedPaths, and the bundle/mirror lock, the argocd-helm render guard, and the hydrating gate all consume the effective set. The closure is recomputed at every boundary, never persisted. The hydrating gate runs the shared coherence rules for EVERY closure-triggering profile: the single evaluator (allocpolicy.CheckCoherence) carries the full NVIDIA#1327 tuple verdicts for both advertiser shapes — under external, dual advertisement (devicePlugin.enabled=true, DRA gpus.enabled=true) and the inert waiver reject; under the empty (operator-managed) shape it mirrors the resolver's non-external contract verbatim (chart guard, dual advertisement, no advertiser, inert waiver) — and the resolver delegates its verdicts to the same evaluator, so gate and resolver cannot drift. Profiles that do not own advertisement (the AKS shape) keep today's WARN semantics — locks follow ownership. ResolveGPUAllocationPolicy counts a declared external advertiser as THE advertiser in the exactly-one invariant; the resolved policy value stays device-plugin-extended-resource. Evidence predicate v2: profile-bearing recipes emit https://aicr.run/recipe-evidence/v2 carrying {selection, advertiser, policyDescriptorIdentity}; unprofiled evidence stays byte-identical on v1. Producers, the pointer, all four verifier checkpoints, the publisher, and testgrid-publish enforce the bidirectional contract, and the verifier rejects a descriptor-identity mismatch as historical-only. No committed evidence pointer is profiled yet, and none is rejected by the new checkpoints; the two committed GKE pointers (h100-gke-cos-training) are v1 evidence over the now-profiled recipe, so the cut-over demotes them to historical — recipe-health Evidence links move to pending until the planned per-value v2 re-signing. Fail-closed behavior changes, all per the ADR: GKE compositions now reject on /v1 endpoints (profile cut-over); snapshot-based GKE generation requires NodeTopology label readings (an unavailable reading fails with the distinguishable DD1 diagnostic); a bundlers subset omitting a closure-locked component (nvidia-dra-driver-gpu on GKE) fails. Docs: a new integrator page, docs/integrator/gke-gpu-setup.md, mirrors the AKS GPU Setup structure (two-mode table, snapshot-verification story, qualification and selection-vs-verification matrices, default and operator-managed procedures, troubleshooting); the component catalog keeps the ownership model and links the procedures there. Also updated: validation, cli-reference, api-reference (GKE /v1 cut-over; batch example moved to /v2), automation, recipe-development, data-extension, contributor/recipe, contributor/validator. GCP UAT asserts pin v1alpha3 + selectedProfile. Default flip + review rounds: gcp-managed is the default because the opt-out label forfeits GKE's managed driver install (the install is finalized by an init container of the same kube-system DaemonSet the label disables), so operator-managed requires Google's standalone nvidia-driver-installer DaemonSet with pools created gpu-driver-version=disabled; the docs and ADR record the amended reality. Review fixes: descriptor currentness now runs on the Sigstore-verified predicate path too, and the verifier binds the predicate profile block (presence, selection, advertiser) to the manifest-verified recipe; policyDescriptorIdentity is recipe-scoped (allocpolicy.IdentityFor over the recipe's closure-contributing entries) so an unrelated descriptor expansion cannot invalidate evidence; BuildPointer and Publish validate bundle-profile/predicate coherence before any registry side effect; testgrid-publish runs the shared predicate-type coherence check; ResolveGPUAllocationPolicy validates the advertiser vocabulary for typed Go callers; the OpenAPI selectedProfile schema and Go facade docs admit advertiser: external. Earlier round: rebased onto NVIDIA#2047 (merged), which had withdrawn the NVIDIA#1755 gate and removed the opt-out label from the GCP UAT cluster-config — the rebased overlay, catalog, validation, and ADR DD2 text now state the final per-value enforcement the profile lands, and the label-free UAT pool matches the gcp-managed default. Under advertiser: external, ResolveGPUAllocationPolicy aggregates devicePlugin.enabled across every enabled operator component (gpu-operator AND gpu-operator-ocp, OR semantics mirroring the recipe-side gate) instead of the warn-and-prefer single read; the publish-time bundle coherence gate also compares the advertiser; both statement builders validate predicate-type coherence producer-side; the pushed bundle's OCI description derives from the actual predicate type; allocpolicy identity canonicalization merges duplicate-component entries (pinned identities unchanged); EffectiveLockSet no longer panics for typed callers that skipped artifact validation; ValidateAdvertiser errors propagate un-double-wrapped; the GKE-COS driver-absent remediation (bundler + client copies) is per-profile-value; stale reserved- advertiser and pre-profile claims swept from code comments and docs. This round: the hydrating coherence gate fails closed on an enabled DRA component whose resources.gpus.enabled is absent (the pinned chart's declared default is true, so treating absence as unknown let a gcp-managed artifact with custom values omitting the stock pin pass generation and bundling while deploying a dual advertiser), mirroring the validation-time resolver; the publish/pointer coherence gate also compares the predicate's policyDescriptorIdentity against the identity recomputed from the bundle recipe, so the split-leg publish cannot spend a Fulcio cert, a Rekor entry, and an OCI push on evidence its own verifier rejects as historical-only; the recipe-health constraints_wellformed dimension mirrors constraints.Evaluate's node-set dispatch (new exported ValidateGPUNodesLabelValue) so the negated NVIDIA#1755 form this PR introduces no longer falsely fails every GKE recipe; presence.yaml drops the unsuffixed GKE dashboard entries (profiled runs land on -gpustack-<value> routes, AKS precedent) and recipe-health.md is regenerated; the secure-access GPU probe's PATH/LD_LIBRARY_PATH exports use ${VAR:+${VAR}:} guards so an unset variable cannot leave a leading empty (CWD) search-path entry; the pushed bundle's OCI description derivation is extracted and table-tested; docs: the ADR's normative /v2 POST example uses the shipped gcp-managed value, api-reference scopes the /v1 rejection to /v1/recipe and /v1/query (/v1/bundle rejects only profile-bearing bodies), the attestation package godoc covers the v2 predicate, and the gke-gpu-setup retrofit procedure now switches the pool's driver mode to gpu-driver-version=disabled (node-pools update --accelerator) before relying on the standalone installer — Google's nvidia-driver-installer DaemonSet ignores automatic-install pools, so deploying it over a gpu-driver-version=default pool left replacement nodes driverless. Live-qualification round: gcp-managed qualification on a GKE H100 cluster came back deployment 4/4 and conformance 11/11 green (NCCL all-reduce 338 GB/s; secure-accelerator-access passed both intents), but every inference-perf vLLM worker crash-looped with "Failed to infer device type" — GKE's managed device plugin injects the /usr/local/nvidia driver mount through its device-plugin Allocate response without setting LD_LIBRARY_PATH, so libcuda.so.1 is invisible to the dynamic loader (A/B on the crashing worker: LD_LIBRARY_PATH=/usr/local/nvidia/lib64 alone flips torch CUDA to available). Both Dynamo deployment templates now wrap the worker command in a shell that appends /usr/local/nvidia/lib64 to LD_LIBRARY_PATH with the ${VAR:+} guard — a pod-level env override would clobber the image's own LD_LIBRARY_PATH (nixl/ucx/cuda entries), and the append is a no-op under the operator toolkit flow, so no platform branching is needed (pinned by TestParseDynamoTemplate_WorkerDriverLibPathAppend). Review round: the GKE retrofit runbook reorders to driver-path-first/label-last — installer, driver-mode switch, verification, then the label handoff — because the label-first order left autoscaled/repaired nodes driverless until the driver-mode switch, and it documents rollback (remove the label); the evidence-refresh doc makes --platform conditional on the target leaf's criteria and records that legacy v1 pointers carry no profile field (a v1->v2 conversion selects the profile from the target cluster, not the pointer); the allocation-policy resolver suppresses the warn-and-prefer diagnostic under a declared external advertiser, whose branch aggregates both operator components; the data-extension heading drops its stale "(AKS example)" suffix (no inbound anchor links); a pointer_test comment references the real coherence matrix (TestValidateBundleProfileCoherence). Blocking-review round: the hydrating artifact gate applied the shared tuple evaluation only under a declared external advertiser, so a forged operator-managed artifact enabling DRA whole-GPU advertisement next to the operator's plugin (or carrying an inert chart-guard waiver) passed PrepareAndValidateWithContext and bundled while ResolveGPUAllocationPolicy rejected the identical state — the gate/resolver symmetry gap. allocpolicy.CheckCoherence is now the single full-tuple evaluator for both advertiser shapes (Observation gains the gpuResourcesEnabledOverride reading and the diagnostic operator-component name), the resolver's external and non-external branches both delegate their verdicts to it (policy SELECTION stays in the resolver), and the gate feeds it the waiver reading plus the resolver's exact per-advertiser operator semantics (OR-aggregation under external, warn-and-prefer under empty); regression pins the forged operator-managed dual-advertisement, chart-guard, and inert-waiver rejections plus the stock operator-managed happy path. The argocd-helm render guard needs no equivalent: it locks paths from EffectiveLockSet and performs no tuple evaluation — the verdict comes from this gate before generation. Evidence verifier: for InputFormDir the bundle directory is caller-owned, and checkRecipeIdentity reopened recipe.yaml by path after CheckInventory hashed it — a TOCTOU window (CWE-367) in which a swapped file made identity accept bytes the manifest never covered. The inventory pass now captures the recipe bytes in the same single read that hashes them (checkInventoryCaptureRecipe), checkRecipeIdentity consumes those captured bytes and fails closed when none were captured, and a deterministic swap regression (inventory accepts recipe A, file replaced with B, predicate B rejected against the captured A bytes) pins the closure. Docs: the gke-gpu-setup retrofit handoff is one coherent sequence — standalone driver ready first, then the opt-out label, then the GPU Operator deploy-and-wait (new step 5) — explicitly accepting the brief advertiser-free window instead of a dual-advertiser overlap, and the "No advertiser at all" troubleshooting entry now pins the same order instead of advising the plugin be deployed before the label. Refs NVIDIA#1761, NVIDIA#1755, NVIDIA#1716, NVIDIA#1327 Signed-off-by: Yuan Chen <yuanchen97@gmail.com>
Summary
Adds the fail-closed GKE device-plugin ownership check from #1755: a new node-set constraint form (
NodeTopology.gpu-nodes.label) evaluated ataicr validatereadiness, wired into the GKE-COS overlays so an unlabeled GPU node pool fails validation closed (exit 2) with an actionable diagnostic before any phase runs.Motivation / Context
AICR's GKE recipes ship the GPU Operator with
devicePlugin.enabled: true, while a default-provisioned GKE GPU node pool also runs GKE's managed device plugin — two advertisers ofnvidia.com/gpuper node, with nondeterministic ownership. Until now nothing detected the conflict:aicr bundleis offline, and the generic constraint syntax compares scalar readings, so "every GPU node carriesgke-no-default-nvidia-gpu-device-plugin=true" was inexpressible. PR #1998 documented the prerequisite; this PR enforces it.Scope position (ADR-015 Deferred Decision 2 — marked resolved in the ADR by this PR). The reusable node-set constraint form — including the negated direction — lands here under #1755, exactly as the ADR reads it. The follow-up GKE
gpuStackprofile recipes (#1761 adoption step) will consume the form unchanged when they land:operatoruses the positive predicate,csp-managedthe negated one. Nothing in this PR touches the profile mechanism, theConstraintschema, the OpenAPIpropertyNamesallowlists, or the v1alpha2/v1alpha3 version gate.Merge order / dependency: resolved — #1998 merged first (2026-08-04) and this PR is rebased onto that main. The component-catalog paragraph now describes the readiness gate (
NodeTopology.gpu-nodes.label, fail-closed, exit 2) instead of claiming no deterministic check exists, closing the loop the readiness remediation text points into.Review-round fixes folded in at head: deprecated apimachinery label validators replaced with
content.IsLabelKey/content.IsLabelValue(the vendored v0.36.3 marks the old aliases deprecated); aValidatePhasesreadiness-preflight test pinning the default client path fail-closed (the plural entry point previously had zero coverage on that branch); an encode→decode round-trip test running realtopology.Collectoroutput (plain, disambiguated, truncated shapes) through the evaluator so the cross-package wire format cannot silently diverge before #2003; parse-validation table rows for a valid key with an invalid label value;key=(empty value) accepted as a valid positive form — Kubernetes permits empty label values, the collector already encodes them (|<nodes>), and the disambiguated map key<key>.cannot collide with a real label key (keys may not end in a dot) — pinned by table rows for the uniform and mixed shapes plus a collector round-trip leg; and a comment documenting checkReadiness's deliberate error-code flattening.Fixes: #1755
Related: #1761, #1998 (merge first — see above)
Type of Change
Component(s) Affected
cmd/aicr,pkg/cli)cmd/aicrd,pkg/server)pkg/recipe)pkg/bundler,pkg/component/*)pkg/collector,pkg/snapshotter) — exported truncation predicate onlypkg/validator)pkg/errors,pkg/k8s)docs/,examples/)recipes/overlays/gke-cos.yaml,tests/uat/gcp/cluster-config.yaml,pkg/constraintsImplementation Notes
The form is name-dispatched, not schema-extended.
constraints.Evaluatedispatches on the exact nameNodeTopology.gpu-nodes.labelbefore the scalar path (precedent: thenccl-benchmark-*sentinel names). The constraint staysname+value, so both existing call sites (readiness, overlay/profile evaluation) get the form with no API or artifact-version change.Value grammar is
<label-key>=<value>(every GPU node carries it) or!<label-key>(no GPU node carries the key), validated with the vendored Kubernetes label validators (content.IsLabelKey/content.IsLabelValue) — so the scalar operator grammar (>= x,!= x) cannot be misread as a key/value predicate, and a key no node can legally carry (e.g. a double slash) is rejected instead of making the negated predicate pass vacuously.GPU-node universe = nodes under the snapshot's
cloud.google.com/gke-accelerator*label readings — GKE's native signal, present from pool creation before the Operator or NFD run (NFD'snvidia.com/gpu.*labels don't exist on the pre-deployment cluster this check validates). Per the issue's acceptance requirements, both predicate directions fail closed on: truncated node lists (--max-nodes-per-entrysnapshots), an empty universe (no vacuous pass), missingNodeTopology.labelreadings, and mixed/disambiguated label values (key.valueencoding). Disambiguated-shape decoding enforcesencodeLabels' invariants — plain and disambiguated forms never coexist, and genuine disambiguation always yields at least two entries — so a distinct dotted label whose value equals its own suffix (e.g. a literal<key>.true=truelabel) cannot satisfy the predicate; an accepted disambiguated set must additionally partition its nodes — overlapping node sets prove the encoder's<key>.<value>map key collided with a real label of that literal name (one reading silently overwritten, #2003) and fail closed as an ambiguous reading. A single disambiguated-shape entry without the plain key (a possible collision remnant) and structurally malformed readings (missing or extra|separators, empty node lists, node tokens that are not canonical RFC-1123 node names) also fail closed — both would otherwise let the negated predicate pass vacuously. The residual ambiguity (collisions with identical node sets, or every disambiguated entry overwritten) is what the lossy encoding cannot express; the lossless-encoding root-cause fix is tracked in #2003, structured truncation metadata in #2002. The truncation detector is owned by the collector (topology.IsTruncatedNodeList, next toformatNodeList, with a round-trip test) so the format and its detector cannot drift apart.Both validator entry points are gated.
checkReadinessruns inValidatePhasesand in the per-phaseValidatePhase(the exported SDK pathClient.ValidateStatedocumentation directs per-phase callers to) — a single-phase caller cannot bypass the recipe's readiness constraints, and declared readiness constraints with a nil snapshot fail closed instead of silently skipping. Both pinned by tests.Wiring: readiness, not
spec.constraints. A top-level constraint would exclude the GKE overlays during snapshot-based generation on exactly the unlabeled cluster the diagnostic exists to fix. The check lives in the GKE-COS base overlay'svalidation.readiness.constraints;checkReadinessnow evaluates readiness-phase constraints alongside the top-level set (they were declared, merged, and carried intoValidationInputbut never consumed) and appends the constraint'sremediationto the failure message — that's where the issue's required device-plugin diagnostic lives.UAT cluster config. The GCP UAT GPU pool (
tests/uat/gcp/cluster-config.yaml) predates the documented prerequisite and does not carry the label; without fixing it this PR would fail the GKE UAT lane. The label is added to the pool'snodeConfig.labels. Note the config actuates at cluster provision time — the nightly lifecycle (provision→CUJ→teardown) picks it up on the next run; a held daytime cluster would need re-provisioning or a manualgcloud container node-pools update.Sequencing with #1998. #1998 merges first; this PR rebases on it and refreshes the component-catalog paragraph (added there) that states "AICR has no deterministic check for a violation today" — kept out of this PR until the rebase to avoid cross-PR conflicts.
Testing
Coverage (per-package, current vs origin/main baseline):
pkg/constraints: 97.9% → 97.9% (flat)pkg/validator: 48.5% → 50.9% (+2.4%)pkg/collector/topology: 84.2% → 84.4% (+0.2%)CLI end-to-end (branch-built binary, no cluster): generated the GKE H100 training recipe with
aicr recipe— the hydrated artifact carries the readiness constraint with its remediation — then ranaicr validate --no-clusteragainst four crafted snapshots: labeled pool → exit 0 ("all 2 GPU node(s) carry …"); unlabeled pool → exit 2 naming the offending nodes plus the full device-plugin remediation; truncated snapshot → exit 2 with the regenerate-without---max-nodes-per-entryinstruction; no GPU nodes → exit 2 with the empty-universe diagnostic. Live-cluster validation deliberately skipped: the gate is inline (no cluster I/O), and the first post-merge nightly UAT provisions from this PR's cluster-config and runsaicr validatethrough the gate, providing the live confirmation automatically.New tests: table-driven coverage of both predicate directions × {pass, fail, mixed values via disambiguated keys, multiple accelerator types, non-GPU nodes carrying the label, truncated universe reading, truncated target reading (both directions), empty universe (both directions), missing label subtype/measurement/nil snapshot, prefix-collision non-misattribution, scalar-grammar rejection, malformed values}; validator-level test that readiness-phase constraints are evaluated and carry remediation into the failure.
New exported symbols: the
GPUNodesLabelConstraintNameconst andtopology.IsTruncatedNodeList(covered by the round-trip test).make qualifywas run unsandboxed after verifying the sandbox-only failures (cleanup_test.shstubs,httptestport binds) reproduce identically on a cleanorigin/maincheckout.Risk Assessment
Rollout notes: A pre-change
aicrbinary evaluates only top-level constraints and silently ignoresvalidation.readiness.constraintsin a recipe generated at this commit (version skew is advisory-only) — validate recipes with a matching or newer binary.aicr validateagainst a GKE cluster whose GPU pools lackgke-no-default-nvidia-gpu-device-plugin=truenow fails readiness closed (exit 2) — intended and documented, but visible to existing users of unlabeled clusters; the failure message carries the exact remediation. Snapshots captured with--max-nodes-per-entryalso fail this constraint closed with a regenerate instruction. Recipe generation and bundling are unaffected. The UAT GKE pool config is updated in this PR; held daytime UAT clusters need one re-provision (or a manual pool update) before their next validate run.Checklist
make testwith-race)make lint)git commit -S) — GPG signing info