feat(authz): probe verdicts and listing-filter mechanics part 1/4 (S09 Enforcement plan: 19/20)
Part 1 of 4 of the S09 enforcement plan — Step 19: Permission-filtered repository listing.
The step is split into 4 stacked MRs to keep each within the review size limit (≈800 ideal). Each part targets the previous one; the stack sits on step 18 part 4 (!2146 (merged)), so part 1 becomes mergeable once that MR lands. Review and merge bottom-up.
Stacked MRs (review/merge bottom-up)
- feat(authz): probe verdicts and listing-filter ... (!2251 - merged) • David Fernandez • 19.4
👈 - feat(managementapi): probe-dispatched listing b... (!2252 - merged) • David Fernandez • 19.4
- test(managementapi): filtered listing behavior ... (!2253 - merged) • David Fernandez • 19.4
- feat(managementapi): wire the listing filter se... (!2254 - merged) • David Fernandez • 19.4
This part (~1,565 reviewable LoC: ~530 source, ~1,035 test, measured at 729e2c8a)
The foundations both later parts build on:
authz.Evaluator.Probe— the point-check verdict without booking the denial metric or log, for a caller that branches on the verdict rather than answering with it.denials_totalkeeps its meaning ("masked-404 requests the decision layer denied") because the listing branch decision no longer books a denial for requests that answer 200. The IAM-contract-violation WARN (non-empty tuples, empty principal) fires under bothEvaluateandProbe.- The GLAZ
BatchCheckcall raises its per-call receive limit to 16 MiB, sized for a full listing chunk's response with populated reasons. internal/managementapi's filter mechanics, not yet reachable from any route (fetchFilteredListPagecarries anolint:unusedthat part 2 removes when it wires the caller): chunked candidate enumeration (listingChunkSize= 9,998 — the IAMReadRelationshipsobjects cap is 10,000, protovalidate-enforced inthird_party/iam-proto/proto/lookup/lookup.proto, and every chunk read also carries the namespace and organization ancestors), uncached tuple reads, greedy relationship packing under the GLAZ item and relationship caps, BatchCheck response validation matched onItemIndex+Actionwith the object-echo check, cross-chunk survivor dedupe, the resume-walk for a page whose rows all vanish between enumeration and fetch, and fail-closed arms for every contract violation (503, never a partial list).
Unit coverage rides in the same part: internal/managementapi/list_authz_internal_test.go (packing, validation, chunk loop, fail-closed arms) and the internal/authz Probe-vs-Evaluate booking tests.
External dependencies the plan gates this step on, confirmed: the deployed GLAZ sidecar is v2.3.1 (.runway/fairway.yaml), which serves BatchCheck (v2.0.0+); the IAM request limit is the protovalidate cap above.
Size
The whole step is ~3,630 reviewable LoC (~675 source, ~2,955 test, measured at 729e2c8a plus the three parts stacked above it) against the plan's ≈230/≈550 estimate — the growth is the authz Probe groundwork, the wiring seams, and the review-hardened validation plus its test matrix. This part exceeds the 500 LoC guideline because the mechanics and their unit tests are one indivisible feature: the packing/validation invariants are only meaningful against the chunk loop that feeds them.
References
- Plan: docs/plans/2026-08-21-s09-authorization-enforcement.md
- Spec: S09 (authorization), Repository listing
No e2e scenario is added or affected: no catalog covers the management API, and the listing filter's behavior is pinned by this step's handler tests (plan Testing Strategy).
Related to #861 (closed)