Future deepening: extract the status application + deepen the Flow module #54
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
lab/authz-bridge#54
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Context
The leader status-write race (concurrent Builds from the three per-kind controllers + the TTL refresh racing on the CRD statuses and the managed Registry Secret) was fixed in
internal/registry/state_loader.go:buildMuserializes whole Builds, andapplyStatuseswraps each status update inretry.RetryOnConflictwith a fresh-copy re-check. The behavior is pinned by the envtest concurrency canary ininternal/controller/providers/build_concurrency_test.go(8 goroutines × 3 Builds + an external writer; red pre-fix, green post-fix).This issue tracks the two follow-on deepening opportunities from the architecture review of 16 Sep 2026. They are independent; each is a self-contained piece of work.
Status (updated 21 Sep 2026)
115ed51).buildMuserializes whole Builds andapplyStatuseswraps each status update inretry.RetryOnConflictwith a fresh-copy re-check; pinned by the envtest canary ininternal/controller/providers/build_concurrency_test.go.f88415a).App.OIDCAvailable()/App.ExchangeAvailable()exist and are used by the OIDC/Exchange gates andSnapshot.Servable(). The callback's requirement (TokenEndpoint+JWKS) is still inlined incallbackUnavailableReason(internal/serve/callback.go) — "finish moving availability onto the App" is still pending for the callback.applyStatuses/statusChanged/mergeConditionsare still on theBuilderin state_loader.go (no extracted module); there is no sharedresolveApp— the three host-resolving Flows still each callSnapshot.AppForHostnamedirectly.1. Extract the status application from the leader's Builder
The
Builder(state_loader.go) still mixes eight responsibilities: leader gating, Cookie-key bootstrap, cluster listing, referenced-Secret checks, IdP resolution, blob encoding, Secret write, and status application. The status application (applyStatuses+statusChanged+mergeConditions) is a self-contained behavior with its own invariants and deserves its own module with a small interface (e.g.Apply(ctx, statuses)), leaving theBuildera thinner orchestrator over the pureBuild. Extracting the Cookie-key bootstrap (ensureCookieKeySecret, with its create-if-absent + BYO invariants) is the natural second step.Constraints settled during the design discussion (do not re-litigate without cause):
RefreshInterval(5m), and the TTL build's all-kinds write would break single-writer anyway.RetryOnConflict+ fresh-copy re-check semantics (skip the write when the status is already current — no resourceVersion bump).statusChangedList-based fast path outside the retry (avoids a Get per object in the common case).2. Deepen the Flow module
The Flow concept — resolve the App, gate on the fail-closed model (ADR 0011), verify, respond — has no module. Four one-method structs in
internal/serve(oidcService,callbackService,exchangeService,logoutService) each carry their own copy of: the availability gate (three near-identical*UnavailableReasonfunctions), App resolution (three host strategies — gRPC host verbatim, logout port-stripped, callback by appKey — plusSnapshot.AppForHostnameis an exact-match index with no port normalization), and response shaping. Every single-Flow test today brings up the wholeServer(three listeners, four structs, a real gRPC dial) to exercise one handler.State at the time of writing (the working tree already moved part-way):
http.Clientis consolidated on theServer(the three duplicated clients are gone).App.OIDCAvailable()/App.ExchangeAvailable()exist ininternal/snapshot/snapshot.goand are used by the OIDC/Exchange gates andSnapshot.Servable(). The callback's requirement (TokenEndpoint+JWKS) is still inlined incallbackUnavailableReasonin serve.Direction settled during the design discussion:
Checkvs HTTPServeHTTP) — an abstracted request/decision type was rejected as a shallow abstraction (it would have to express status codes, headers, set-cookie, redirects, and gRPCdenied_response).resolveAppin serve that strips the port before the exact-match lookup, used by all host-resolving Flows (unifies the gRPC verbatim vs logout port-stripped split; normalizing insideSnapshot.AppForHostnamewas rejected as a serving concern in the snapshot package).renew,authorizationCodeExchange,tokenExchange, with three divergent test doubles) are a separate candidate: one Token Endpoint module whose interface isGrant(ctx, grant) → tokens, hiding the x/oauth2 + hstern trio (ADR 0013 pins the libraries, not the module shape) and the Public-client secret rule, with two adapters at the seam (real HTTP, in-memory double).Test goal: single-Flow tests cross the Flow's interface (Snapshot + token-endpoint double) instead of bringing up the whole
Server;Server-level tests shrink to wiring coverage (listener bind, callback/logout dispatch, warm loop).Pointed-at references
docs/references/kanidm-service-account-token-exchange.md(Exchange Flow semantics) before touching the Exchange Flow.docs/agents/deploy.mdfor live deploy/test on the portable cluster.