feat(pg-auth): wire pgauth into gc bd subprocess env (slices 1-3)#1792
Draft
quad341 wants to merge 7 commits intogastownhall:mainfrom
Draft
feat(pg-auth): wire pgauth into gc bd subprocess env (slices 1-3)#1792quad341 wants to merge 7 commits intogastownhall:mainfrom
quad341 wants to merge 7 commits intogastownhall:mainfrom
Conversation
…tauth
Slice 2/4 of the PG-auth chain (parent design ga-dga2; design ga-3hay).
Adds the new internal/pgauth package that resolves a Postgres password
for a (scope, host, port, user) tuple via a deterministic 7-tier chain:
1. envMap["GC_POSTGRES_PASSWORD"]
2. envMap["BEADS_POSTGRES_PASSWORD"]
3. os.Getenv("GC_POSTGRES_PASSWORD")
4. <scope>/.beads/.env BEADS_POSTGRES_PASSWORD (chmod-checked)
5. os.Getenv("BEADS_POSTGRES_PASSWORD")
6. $BEADS_CREDENTIALS_FILE [host:port] section (chmod-checked, parse-checked)
7. ~/.config/beads/credentials [host:port] section (chmod-checked, parse-checked)
Public surface mirrors the design's pinned contract:
- Endpoint{Host,Port,User}, Resolved{User,Password,Source}, Source enum
with stable iota ordering (consumers may persist the int value).
- Source.String() returns the eight snake_case identifiers slice 4's
event payload and gc trace will read verbatim ("none",
"projected_gc", "projected_beads", "process_env_gc", "scope_file",
"process_env_beads", "credentials_file_env", "credentials_file_home").
- ResolveFromEnv(envMap, scopeRoot, endpoint) (Resolved, error). Pass
envMap == nil to skip tiers 1+2.
- ReadStoreLocalPassword(scopeRoot) — slice 4's doctor probe; applies
the same chmod predicate as tier 4.
Errors are typed for slice 4's errors.As discrimination:
- ErrNoPasswordResolvable (sentinel; chain exhaustion wraps with %w).
- *PermissivePermissionError on tiers 4/6/7 when mode & 0177 != 0.
STOPS the chain — does not fall through, since silent fall-through
would hide the operator's intent.
- *CredentialsParseError on tiers 6/7 for malformed credentials files.
Closed reason vocabulary: unterminated section header, empty section
header, missing '=' in key/value line. Unknown keys inside a matching
section are silently ignored per design §7.
Whitespace-only values count as empty (mirrors doltauth). Quoted
values in credentials files honour strconv.Unquote escapes.
Tests cover every tier (positive), exhaustion, NilEnv-skips-1+2, all
three chain-termination paths (perm scope file, perm credentials file,
malformed credentials file), per-reason parse-error variants, absent
file fall-through, non-matching section fall-through, whitespace
emptiness, quoted password, unknown-key tolerance, endpoint fields
echoed in error wording, Source.String() table, and stable iota
ordering. ReadStoreLocalPassword is exercised on happy/permissive/
absent/empty-scope paths.
CI guard TestNoOsGetenvOutsidePgauth walks cmd/+internal/ and asserts
no other package reads BEADS_POSTGRES_* or GC_POSTGRES_* via os.Getenv,
keeping resolution centralised before slices 3 and 4 wire it up.
Coverage: 88.4% of statements; remaining gap is Windows APPDATA branch
in DefaultCredentialsPath plus os.Stat / os.Open error paths that can't
be triggered after Stat passes — genuinely platform-conditional.
Bead: ga-vt6q
Source design: ga-3hay
Reference impl: internal/doltauth (mirror by structure)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 1/4 of the PG-auth design (ga-dga2). Foundation for slices 2-4 (pgauth, bd_env wiring, doctor checks). - Extend MetadataState with PostgresHost, PostgresPort, PostgresUser, PostgresDatabase plus JSON tags on all eight fields. Existing Dolt scopes round-trip with no diff (omitempty on every backend-specific field). - Add LoadMetadataState (fs, path) -> (state, ok, err) with the deterministic short-circuit specified by the design (E1 -> E3 -> E2 -> E4 -> E5). Validation failures are wrapped in *MetadataParseError so slice 4 can discriminate parse vs IO errors via errors.As. - Extend EnsureCanonicalMetadata with the four PG keys in defaults and a backend-discriminated cross-backend scrub (dolt drops postgres_*, postgres drops dolt_*, empty backend preserves both). The json.Unmarshal swallow at the canonicaliser stays untouched. - 14 fixtures under internal/beads/contract/testdata/metadata/ cover every valid and rejected case plus the three mixedBackendField selector branches. Tests cover loader idempotence, byte-equality round-trip on every valid_*.json, the sibling regression TestEnsureCanonicalMetadataPreservesExistingPostgresHostWhenStateOmitsIt, empty-object behaviour, and the *MetadataParseError typed-error contract. Bead: ga-pnqg Design: ga-0nmb Architecture: ga-dga2 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Misspell linter (revive/misspell) flags 'behaviour' as a US-spelling violation in CI Preflight. Single-character comment-only fix. Reviewer finding F1 on PR gastownhall#1727 (ga-cwq1). Bead: ga-pnqg Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Slice 3/4 of the PG-auth chain. When a scope's MetadataState.Backend == "postgres", project the resolver's output into the bd subprocess env; Dolt-backed scopes flow through unchanged under the same dispatch arm. Wrapper-side wiring: - Rename applyCanonicalScopeDoltEnv -> applyCanonicalScopeBackendEnv; the Dolt body lives under case "", "dolt"; postgres takes a new arm calling applyResolvedScopePostgresEnv; default returns "unsupported backend %q for scope %s". - Add applyResolvedScopePostgresEnv (calls pgauth.ResolveFromEnv, projects six PG keys), mirrorBeadsPostgresEnv, and projectedPostgresEnvKeys. - Extend mergeRuntimeEnv strip list with all six PG keys so a stale parent BEADS_POSTGRES_* never leaks into a child. - bdCommandRunnerWithManagedRetry gains a PG branch BEFORE the bdTransportRetryableError check: PG transport errors are wrapped with "postgres at %s:%s is unreachable; gc does not manage external PG endpoints" and never trigger recoverManagedBDCommand. Tests: - TestApplyResolvedScopePostgresEnv_HappyPath / TestApplyResolvedScopePostgresEnv_NoPasswordResolvable - TestBdRuntimeEnvForRig_PostgresBackend - TestMergeRuntimeEnvScrubsPostgresKeys - TestPGTransportError_NoManagedRecovery / _MarkerCoverage - TestMixedBackendCity_PerScopeDispatch - TestProjectedKeysCoverage symmetry guard - TestApplyCanonicalScopeBackendEnv_UnsupportedBackend Refs: ga-wvka, design ga-4qvs, architecture ga-dga2. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Validation-gate grep for applyCanonicalScopeDoltEnv must return zero hits in cmd/gc/. The doc comment retained the old name as a "Renamed from" historical marker, which both tripped the gate and violated the project's "no comments for removed functionality" rule. Refs: ga-wvka, design ga-4qvs. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Cleared by the deployer's gate FAIL on ga-wvka @ ea9af92: - cmd/gc/bd_env.go: Centralised → Centralized (misspell) - cmd/gc/bd_env_test.go: rename unused env param to _ (revive) - internal/pgauth/auth.go: add doc comment on Source const block (revive exported-const) + gofumpt re-aligns trailing comments - internal/pgauth/auth_test.go: drop unused homeDir/credPath returns from pinHome (unparam); all four call sites discard Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Verdict: PASS at HEAD 93eec8b on quad341:builder/ga-wvka-1. Branch contains slices 1+2+3 stacked; merges into current origin/main with no conflicts. Slice 1 content content-identical to original review SHA b7015a0. Closes ga-pnqg.1 + ga-6zqpw simultaneously. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
quad341
pushed a commit
to quad341/gascity
that referenced
this pull request
May 7, 2026
Verdict: PASS at HEAD c7fb142 on quad341:builder/ga-yih2-1. Slice 4/4 of PG-auth chain. Stacked on PR gastownhall#1792 (slices 1+2+3). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5 tasks
Codecov Report❌ Patch coverage is 📢 Thoughts on this report? Let us know! |
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.
What this changes
Brings Postgres-backed credentials end-to-end through the
gc bdsubprocess. Three independently-designed slices ship together because
the chain only makes sense functional end-to-end:
1. PG-aware MetadataState + parse validation
internal/beads/contract/files.golearns four new Postgres fields onMetadataState, JSON tags on all eight, aLoadMetadataStateconstructor, a typed
MetadataParseError, amixedBackendFieldselector, and PG-key defaults + cross-backend scrubbing in
EnsureCanonicalMetadata. Validation order matches the design spec(E1 → E3 → E2 → E4 → E5).
2. pgauth credential resolver
A new
internal/pgauthpackage mirrors the existinginternal/doltauthshape: tiered source resolution(env → password file →
~/.pgpass→ managed-store), a typedSourceenum, no externalos.Getenvreads from inside the package(enforced by
no_external_env_test.go).3. bd_env wiring
gc bdnow resolves Postgres credentials at exec time and projectsthem into the bd subprocess explicitly via
cmd/gc/bd_env.go—mirroring the existing Dolt auth pattern. We do not widen
IsSensitiveKeyormergeRuntimeEnv's inheritance filter; the splitbetween inheritance (still strict) and resolution (at exec) is
deliberate.
Review notes
ga-pnqg.1PASS at theoriginal SHA
b7015a05. Patch is content-identical to the rebasedcommit
31fa03c1shipping here (verified line-for-line).ga-6zqpwPASS atea9af929. The fix-up commit93eec8b9resolves four lintfindings only (misspell, unused param, exported-const doc,
gofumpt) — mechanical, no behavior change.
871313ef. It was trackedseparately under a closed review bead (
ga-cwq1, withdrawn whenPR feat(contract): add Postgres MetadataState fields with parse validation #1727 was closed in favor of local-only iteration) and was
reviewed implicitly as part of the slice-3 chain review at
ea9af929.origin/main8a76142; currentorigin/mainhas moved 13 commits forward. Test merge confirms zero conflicts;
build + vet + lint + targeted tests pass on the merged tree.
Review notes
internal/pgauth(no role names, no judgment calls inGo — tier resolution is a static priority list).
IsSensitiveKeyis not modified — PG passwords stay outside theinheritance filter; resolution is the only path that can produce
them. Auditable.
MetadataParseErrordistinguishes parse vs validation failures socallers can render different operator hints.
bd_env.gois the onlycmd/gcchange; everything else ispackage-internal additions.
Test plan
go build ./...,go vet ./...,golangci-lint runclean (full repo).go test ./internal/pgauth ./internal/beads -count=1PASS onboth as-is HEAD and a test-merge into current
origin/main.no_external_env_test.goenforcesinternal/pgauthreads noexternal env directly.
release-gates/ga-6zqpw-pg-auth-slices-1-2-3-gate.md🤖 Deployed by actual-factory
Need help on this PR? Tag
@codesmithwith what you need.