feat(v2/postgres): export the contract's connection parameters
v2/postgres only exposes a client. The code that turns the contract's PostgreSQL entry plus its projected Secret into connection parameters is private: the format presets (CNPG, Crunchy, Zalando key names), the inline-over-Secret precedence and the pooled-endpoint rule. A service that keeps its own pgx pools, such as GitLab Relay with its per-pool tracing and its separate migration connection, has to reimplement them and drifts from LabKit over time.
ConnectionParams(ctx, infra, opts ...ParamsOption) (Params, error) returns the resolved parameters without constructing a client. Params.DSN() and Params.DirectDSN() render the pooled and the direct endpoint as postgres:// URLs; Params.URL() and Params.DirectURL() return them as *url.URL values for a consumer that edits them first, to keep the credentials out of the DSN or to add query parameters. Password is a secret.Secret and redacts itself when printed. The client builds its DSN from the same methods, so resolution and rendering each have one implementation.
The edge cases that resolve differently than before are stated in the commit body and pinned by tests: the database's own endpoint is required even when a pooler is declared (the proto's rules require it for every loadable contract; only a Secret missing a key its projection names is affected and now fails instead of connecting through the pooler), an empty required value in the Secret (host, port, database name or user) is an ErrMissingConfigField naming the contract field instead of a DSN that pgx resolves through PGHOST or the local socket, an empty projected pooler host or port means "not declared" and keeps the database's value instead of rendering host: (traffic uses the database's own endpoint when both are empty), and an inline port or pool_port of zero counts as unset with a secret_ref too, which only a hand-built contract can carry. A secret-read error other than not-found fails construction rather than silently sending traffic past the pooler; because Params resolves the whole entry, this holds for every consumer, including a client built with WithConnectionPool(false) that never uses the pooler. An empty password behind a declared secret_ref stays valid.
Two shapes were considered and rejected. Returning a *pgx.ConnConfig: pgx parses connection strings but has no builder, a config must originate from ParseConfig, and ParseConfig("") pulls PG* environment defaults into contract resolution; the client's public input is Config.DSN, so this would add a second construction path, and a consumer that applies credentials per connection still needs the fields. Returning a resolved infrapb.PostgreSQL: the proto has no password field, types the port as int32 where the Secret holds text, and its presence semantics would no longer describe what the value holds. Params keeps the proto's field names, drops the pointers, and carries HasPassword and Pooled explicitly instead of by proto presence conventions.
A smaller alternative would export only the preset projections and leave precedence and pooling to callers; this MR prefers one shared resolution.
Stacked on !690 (merged); the redis half follows in its own MR. The first consumer is GitLab Relay (gitlab-org/cluster-integration/gitlab-agent#1091).
Related: #152 (closed)