Repository navigation
feat(libs/go): OpenPool — one authenticated pool, for a service that owns its own tenancy - #206
Merged
Conversation
…owns its own tenancy Every constructor in this package was opinionated. Open splits a service across a reader and a writer and installs the scoped RLS factory; OpenMaintenance selects an application role and refuses a privileged login. A service with one runtime principal and its own authority over whose tenant a call concerns fits neither — so it called pgxpool.New itself, and in doing so re-decided the transport, the trust anchor and the credential, each of which this package already decides. Four services in this fleet did exactly that. That is not four call sites; it is four copies of three decisions, which is why a managed-database or passwordless posture could not be rolled out by changing one thing. OpenPool is the primitive underneath the other two, exposed: the profile decides the transport and the pinned authority, the options decide the credential, the caller gets a pool. It grants no role, installs no session policy and judges no privilege — a caller wanting those wants Open or OpenMaintenance, and this does not become a relaxed route to them. It pings before returning. pgxpool connects lazily, so a pool handed back before its first connection succeeded moves a wrong credential, an unverifiable server or an unreachable address from boot to the first query, where it reads as a runtime fault rather than a deployment one. Every refusal the opinionated constructors make about a capability's transport and credential holds here too — two credentials for one pool, a password beside a provider, a pinned transport with no anchor, an anchor on a transport that verifies nothing, an undeclared issuer, verify-full on a pinned transport — so this is the same decision by a shorter route, not a weaker one. The legacy zero profile stays accepted, so a service adopts the library in one call and changes its TLS posture as a separate, visible step. Not verified against a live server: the success path needs a reachable Postgres and is exercised only by the refusals and the eager-ping behaviour here. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
antoinetoussaint-byte
enabled auto-merge (squash)
October 29, 2026 02:43
antoinetoussaint-byte
added a commit
that referenced
this pull request
Oct 29, 2026
…d its server's authority (#207) * feat(managed-bootstrap): the migration owner declares its identity and its server's authority The follow-up #205 named and deliberately left: bootstrap.Options grew an external-identity declaration and a server trust anchor, but the command could not see them, because the root module pins a published libs/go rather than replacing it with the tree. #206 is on main, so the pin moves and the command reads the two declarations. Delivered as configuration beside the connection they describe, never as flags. A Job's command line is readable by anything in its pod, and these two are the deployment's statement about where this database is. Neither is a secret — an issuer names who mints the credential, and a certificate authority is public information — while a credential minted from the issuer is, which is why it never appears here and is fetched per connection instead. Absent means the password-authenticated behaviour every existing deployment has. This is what lets migrations run against a managed server at all: the database exists but its schema and its application roles do not, and creating a login in the cloud grants CONNECT and nothing else — the in-database roles and grants are the migration owner's work. Verified: the root module builds and vets, and the bootstrap parity suite passes against a real Postgres. One of its four parallel subtests failed first on `all predefined address pools have been fully subnetted` — this machine had 30 Docker networks from concurrent worktrees, and the subtest passes alone. That flake is the environment, not this change. Not verified: no migration has run against a managed instance. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> * fix: prune the superseded library entries go get left in go.sum `go get` adds the new version's hashes without removing the old module version's, and CI's tidiness gate runs `go mod tidy` and compares — so the commit has to carry what tidy produces, not what go get leaves. Three stale lines for the previous libs/go pseudo-version. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> --------- Co-authored-by: Antoine Toussaint <312536060+antoinetoussaint-byte@users.noreply.github.com> Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Follow-up to #205, and the thing that makes it adoptable.
The problem #205 did not solve
#205 put the external-identity declaration and the pinned trust anchor in this library, where they belong. But every constructor here is opinionated:
Opensplits a service across a reader and a writer and installs the scoped RLS factory;OpenMaintenanceselects an application role and refuses a privileged login.A service with one runtime principal, which owns its own tenancy model rather than delegating it to transaction-local RLS settings, fits neither. So it called
pgxpool.Newitself — and in doing so re-decided the transport, the trust anchor and the credential, each of which this package already decides.Four services in this fleet did exactly that. That is not four call sites; it is four copies of three decisions, which is precisely why a managed-database or passwordless posture cannot be rolled out by changing one thing.
Classification
Fix.
OpenPoolis the primitive the other two constructors already build on (parse, install credential, create, ping) — exposed rather than reimplemented. It is not a second mechanism: every refusal the opinionated constructors make about a capability's transport and credential holds here too, so it is the same decision by a shorter route, not a weaker one.What it is
The profile decides the transport and the pinned authority, the options decide the credential, the caller gets a pool. It grants no role, installs no session policy and judges no privilege — a caller wanting those wants
OpenorOpenMaintenance.It pings before returning.
pgxpoolconnects lazily, so a pool handed back before its first connection succeeded moves a wrong credential, an unverifiable server or an unreachable address from boot to the first query, where it reads as a runtime fault rather than the deployment fault it is.The legacy zero profile stays accepted, so a service adopts the library in one call and changes its TLS posture as a separate, visible step rather than as a side effect of adoption.
Reuse
Reused wholesale:
ParseConnectionas the single judge of a connection,resolveExternalIdentityandinstallAccessTokenProviderfor the credential,profileConnectionErrorso an explicit profile still reports a capability as unavailable rather than leaking the driver's text,configuredfor the option surface.Deliberately not done: refactoring
OpenandOpenMaintenanceonto this primitive. They would read better for it, but that is a change to two working, heavily-tested constructors with no behavioural need, and it does not belong in the change that introduces the primitive.Tests
The refusal set is asserted through
OpenPoolitself rather than trusted to be inherited — two credentials for one pool, a password beside a provider, a pinned transport with no anchor, an anchor on a transport that verifies nothing, an undeclared issuer,verify-fullon a pinned transport — plus a nil context and an empty connection, the eager ping against a name that does not resolve, and the zero profile not being rejected as a profile error.Not verified
The success path needs a reachable Postgres and is not exercised here; it is covered only by the refusals and the eager-ping behaviour. No connection was opened to a managed instance by this PR.
Size
~70 hand-written non-test lines, ~110 test lines, one new file plus a README section.