Gateway API v1.5.1 spec compliance matrix¶
Clause-by-clause audit of the implementation against the normative (RFC-2119) surface of the vendored sigs.k8s.io/gateway-api v1.5.1 Standard channel. This is the deliverable the closed audit issue asked for: every implemented resource's normative clauses classified honoured / justified-deviation / violated, with code evidence.
Method¶
- Extracted every MUST / MUST NOT / SHOULD / SHOULD NOT / MAY clause from the vendored godoc — 376 rows in
01-clause-inventory.md, by type. - Added cross-cutting GEP/concept requirements not in field godoc (policy attachment GEP-713, route-attachment semantics) —
02-gep-notes.md. - Classified each clause CRD-enforced / controller-actionable / N/A-tunnel and assessed status MET / PARTIAL / GAP / NA against the real code — per-type detail in
rows-<TYPE>.md. - Ran the official conformance suite (Gateway HTTP + gRPC profiles) against a fresh kind cluster + real Cloudflare test tunnel as pass/fail ground truth.
- Adversarially re-verified every GAP — a skeptic tried to refute each (CRD enforcement, N/A, conditional-satisfied, documented-deviation) before it was allowed to stand. 22 of 25 first-pass GAPs did not survive.
Dashboard (first-pass classification, 376 clauses)¶
| Status | Count |
|---|---|
| MET | 221 |
| PARTIAL | 31 |
| GAP (first pass) | 25 |
| N/A (tunnel architecture / exempt) | 99 |
Conformance ground truth: 76 top-level subtests PASS, 54 SKIP (documented TLS/TCP/UDP/Mesh/WebSocket/GRPCRouteWeight/HTTPS-listener), 0 FAIL (go test ... ok 293s). The suite is green; the audit's value is the normative surface the suite does not exercise.
Adversarial verification: 25 first-pass GAPs → final verdicts¶
| Clauses | First pass | Final verdict | Basis |
|---|---|---|---|
| HR-41, HR-42, HR-43, HR-44, GR-34, GR-35 | GAP (duplicate match-name first-wins not honoured) | DOWNGRADE-CRD | Headers/QueryParams are +listType=map++listMapKey=name (vendor/sigs.k8s.io/gateway-api/apis/v1/httproute_types.go:767-783, grpcroute_types.go:325); the API server rejects duplicate names at admission. Header names match case-insensitively, so case-variant names (Foo/foo) bypass the case-sensitive listMapKey and are ANDed rather than first-wins — a negligible header-only edge (doc note). Query-param names are exact-match per spec, so case-variants are legitimately distinct and there is no residual. |
| SH-47, SH-57 | GAP | CONFIRMED (MUST NOT) | route_status.go:112 Parents = nil + full Status().Update (not SSA) wipes other controllers' RouteParentStatus every reconcile; backendtlspolicy_controller.go:717 preserves foreign entries — route status does not. |
| SH-51, GC-21 | GAP (also GW-81, GW-100, POL-11 same class) | CONFIRMED (MUST NOT), low | No observedGeneration regression guard; status writers stamp ObservedGeneration: generation unconditionally. Get+RetryOnConflict guards resourceVersion only, not a stale-generation overwrite. Narrow race. |
| HR-04 (and GR-16) | GAP | CONFIRMED (MUST), minor | Rule-name uniqueness CEL is experimental-channel only (httproute_types.go:125); shipped Standard CRD strips it; no controller-side uniqueness check. |
| GC-05 | GAP | CONFIRMED but SHOULD | Bad parametersRef is surfaced on Gateway status, not as GatewayClass Accepted=False/InvalidParameters. Defensible design deviation; feeds the SHOULD audit. |
| GC-02 | GAP | RESOLVED (was: CONFIRMED but SHOULD) | gateway-exists-finalizer is now managed by the GatewayClass reconciler (added while any Gateway uses the class, removed when none do). |
| GW-31, GW-87 | GAP | DOWNGRADE-NA | spec.addresses is never user-selectable for a tunnel; same territory as the exempt SupportGatewayStaticAddresses. Doc note only. |
| GW-75, GW-86 | GAP | DOWNGRADE-MET | Precondition is "if empty value NOT supported"; the controller supports empty (claims SupportGatewayAddressEmpty, always auto-assigns the tunnel CNAME), so the obligation is vacuously satisfied. |
| GC-09 | GAP | DOWNGRADE-DEFENSIBLE | Controller reconciles only classes naming its controllerName and supports all of them, so Accepted=True is correct; no "will not support" scenario arises. |
| GC-22 | GAP | DOWNGRADE-CONDITIONAL | Publishing status.supportedFeatures is optional; the "MUST be sorted" clause governs order only if published. Not published → vacuously satisfied. |
| SH-36 | GAP | REFUTED | The "Dropped Rule" PartiallyInvalid approach is implemented and tested (route_status.go:334, route_status_diagnostics_test.go:76); the spec requires only one of two approaches. |
| SH-43 | GAP | DOWNGRADE-CONDITIONAL | The per-reconcile full rebuild re-adds only currently-valid own parentRefs, so stale own-entries are dropped naturally; the SHOULD is satisfied for the realistic case. |
| HR-61 | GAP | DOWNGRADE-NA | Redirect Scheme enum is http;https; both have well-known ports, so the "scheme without well-known port" precondition is unreachable. |
| GR-44, GR-45 | GAP | DOWNGRADE-NA | A GRPCRoute backend is gRPC-over-HTTP/2 by definition; forcing h2c is correct, and the one protocol-relevant signal (TLS via BackendTLSPolicy) is honoured. |
| OR-03 | GAP | DOWNGRADE-DOCUMENTED | ExternalName Service support is a deliberate, documented deviation (limitations.md:10/32/66) with a stated trust-boundary rationale. Recommend adding an explicit CVE-2021-25740 citation. |
Confirmed findings (post-verification)¶
Code bugs (file as kind/bug)¶
- Route status reconcile clobbers other controllers'
RouteParentStatus(SH-47, SH-57; MUST NOT).internal/controller/route_status.go:112resets the wholeParentsslice and writes via fullStatus().Update, so a Route co-managed by another controller loses that controller's parent-status entry every reconcile. Fix: preserve entries whoseControllerNamediffers, mirroringbackendtlspolicy_controller.go:717-762. Highest severity (multi-controller correctness). Related: listener-status rebuild has the same shape (GW-96/GW-98 PARTIAL). - Status writers lack an observedGeneration regression guard (SH-51, GC-21, GW-81, GW-100, POL-11; MUST NOT). Status conditions are stamped with the current generation unconditionally; the spec forbids updating a condition whose stored observedGeneration is greater than the writer's known generation. Mitigated by fresh-Get + RetryOnConflict (narrow race), so low severity. Fix: a shared guard, or an accepted-risk note.
- HTTPRoute/GRPCRoute rule-name uniqueness not enforced (HR-04, GR-16; MUST). The uniqueness CEL is experimental-channel; the shipped Standard CRD omits it and the controller does not validate. Minor. Fix: controller-side validation or a documented limitation.
Documentation additions (justified deviations, recorded in limitations.md by this change)¶
- spec.addresses is not honoured/validated (tunnel address is not user-selectable; same basis as the exempt static-addresses feature) — recorded under Gateway Listener Configuration.
- ExternalName Service support now cites CVE-2021-25740 in its existing trust-boundary rationale.
- Case-variant duplicate header match names (
Foovsfoo) bypass the case-sensitive CRD listMapKey and are ANDed rather than first-wins — negligible header-only edge (query-param names are exact-match, so unaffected); recorded under Route Conflict Resolution.
SHOULD / MAY tiers (verified)¶
The SHOULD and MAY tiers were re-verified in a second pass after the MUST audit — per-type adversarial review for SHOULD, catalogue for MAY. Per-clause detail in shouldmay-<TYPE>.md.
SHOULD / SHOULD NOT (51 clauses)¶
- HONOURED-TESTED (~22) and N/A for the tunnel architecture (~23) account for the bulk.
- HONOURED-TESTED since the audit (was HONOURED-UNTESTED, 7): HR-21, HR-24, HR-63, BTLS-06, SH-31, SH-32, LS-05 — each now pinned by a regression test (explicit-zero timeouts, redirect Location port, BackendTLS HTTP/gRPC equivalence, reason-vocabulary AST guard, ListenerSet status leak guard).
- DEVIATED-DOCUMENTED (5): GW-74, BTLS-04, GC-05, SH-43, OR-03 — permitted deviations with a written rationale in limitations.md.
- DEVIATED-SILENT (originally 3 distinct gaps across 4 clause IDs) — all resolved since the audit: GC-02 and its v1beta1 alias OTHER-45 are HONOURED (the reconciler now manages the gateway-exists-finalizer); GEP-08 (discoverability condition on the policy ancestor status, not the affected Gateway/Service) and HR-61 (no redirect-port fallback to the listener port — unreachable through the Standard CRD scheme enum http/https) are DEVIATED-DOCUMENTED with rationales in limitations.md. Also resolved earlier: GR-44 / GR-45 (gRPC silently dialing cleartext when a Service declared a TLS appProtocol without a BackendTLSPolicy) now fails the backend closed, matching the HTTP path — #438.
MAY (34 clauses)¶
Catalogued implemented / intentionally-omitted; zero worthwhile candidates surfaced. Every MAY is either IMPLEMENTED or OMITTED-INTENTIONAL (edge-terminated TLS, status-only reconciler, single flattened ingress). The optional surface is a deliberate product choice.
Provenance¶
01-clause-inventory.md— verbatim clause extraction (376 rows).02-gep-notes.md— GEP/concept cross-cutting requirements.rows-<TYPE>.md— first-pass per-clause classification + evidence (GW, HR, GR, SH, GC, RG, BTLS, LS, OTHER). For the 25 first-pass GAPs, the verdicts in the verification table above supersede the per-row status.- shouldmay-
.md — verified SHOULD-tier verdicts and MAY catalogue, per type.