Skip to content

feat(spec): refuse a postgres config.url that pg itself cannot parse at publish - #9158

Merged
os-zhuang merged 1 commit into
mainfrom
claude/issue-9091-postgres-url-pg-grammar
Aug 16, 2026
Merged

feat(spec): refuse a postgres config.url that pg itself cannot parse at publish#9158
os-zhuang merged 1 commit into
mainfrom
claude/issue-9091-postgres-url-pg-grammar

Conversation

@os-zhuang

Copy link
Copy Markdown
Contributor

Fixes #9091

What

PostgresConfigSchema.url documented a grammar (postgresql://[user@][host][:port][/dbname][?params]) and enforced none of it: the value only went through the shared credentialFreeUrl / placeholderFree string scans, whose refusal to parse is load-bearing for mongo's multi-host/+srv forms (#8696) — correctly lenient there, no check at all here. Measured on pg@8.22.0: both pg-connection-string's parse and pg's ConnectionParameters throw TypeError [ERR_INVALID_URL] on postgresql://app@h1:5432,h2:5433/app, yet the schema accepted it — the failure surfaced only at connect, as a bare Invalid URL whose input field pg redacts.

Per triage, the fix is a per-driver superRefine on the postgres url only — the shared helper is untouched — that asks pg's own grammar rather than re-modeling it: parse from pg-connection-string@^2.14.0 (the parser pg itself uses; added as a dependency of @objectstack/specservice-datasource already depends on it for the #8873 runtime arm). Refused, each at the value's path:

  1. Anything parse throws on (multi-host DSNs, non-numeric ports, malformed percent-escapes), quoting the parser's own message and naming the multi-host cause plus the single-host/proxy replacement.
  2. A scheme-less non-URL — structurally unusable: parse only "accepts" it by resolving against its placeholder base (postgres://base), so pg would connect to the literal host base with the authored text as the database name. Detection mirrors the parser's own preprocessing (space re-encoding, the @/ empty-host retry), so the two cannot disagree about which branch ran.
  3. The fs-reading query parameters ?sslcert= / ?sslkey= / ?sslrootcert= — these make parse itself call fs.readFileSync, so letting them through would have publish-time validation reading the validating server's filesystem (verdict depends on which machine validates; existence oracle). Named explicitly as the one place this PR refuses something pg can sometimes open: certificate material already has its declared home in the datasource-level ssl block — the exact prescription the config-level ca/cert/key keys carry (SSL_DETAIL_BELONGS_ON_DATASOURCE). Boundary mirrors pg exactly where it can: exact-case keys, non-empty values only (?SSLCERT= and ?sslcert= stay accepted — measured, pg reads neither).

Accept-side, pinned byte-identically: single-host URLs (credential-free included), empty-host libpq forms (postgresql:///db, postgresql://user@/db), unix-socket spellings (leading-/ path, socket:, percent-encoded host), IPv6 hosts, non-credential/non-fs query params — and mongo's multi-host form via MongoConfigSchema (the shared helper's leniency it must keep). Composition pinned: a value violating both the #8082 credential rule and this one reports both issues.

Contract change plumbing

Verification (all at 4f8f302, the final commit)

  • pnpm --filter @objectstack/spec test — 406 files / 10816 passed.
  • pnpm --filter @objectstack/spec typecheck — green.
  • pnpm --filter @objectstack/spec build && check:generated — all artifact gates green after --fix regenerated the one proved-stale artifact (docs).
  • Reverse verification (fix reverted from the committed state, expected direction: red): exactly the 6 new refusal pins fail, all accept-side pins stay green; restored, tree clean vs HEAD.
  • Downstream consumer sweep (prefix direction — consumers of spec, run against the rebuilt dist): @objectstack/service-datasource 478 passed, @objectstack/runtime 2468 passed. Fixture sweep over the rule's consumption radius found no repo fixture authoring a now-refused postgres URL.
  • Gate re-derivation (node scripts/pm/dispatch-gates.mjs <changed paths>) union run: cross-package-test-inputs, doc-formula-expressions (after building @objectstack/formula — fresh-worktree artifact, not this change), docs-audit-scope, docs-redirects, merge-driver, objectui-changeset, quick-reference-counts, release-page-status, role-word, spec-parsed-alias, type-source-resolution, adr-0087-registration, changeset-no-major, empty-changeset, dev-prereqs, query-options-erasure, engine-double-contract, where-matcher, type-check-coverage, type-check-debt --re-measure (workspace closure built first), check-nul-bytes — all green.

Notes for review

Generated by Claude Code


Generated by Claude Code

…at publish (#9091)

PostgresConfigSchema.url documented a grammar it never enforced: the shared
credentialFreeUrl/placeholderFree checks are string scans by design (their
refusal to parse is load-bearing for mongo multi-host/+srv, #8696), so a URL
pg throws on (libpq multi-host DSN, bad port) published green and failed at
connect with a redacted Invalid URL. A per-driver superRefine now asks pg's
own grammar (pg-connection-string parse, new spec dependency), refusing what
parse throws on, scheme-less values parse only resolves against its
placeholder base, and the fs-reading ?sslcert/?sslkey/?sslrootcert params
(publish must not read the validating host's filesystem; certificates live in
the datasource-level ssl block). ADR-0087 semantic entry + registry regen +
docs regen; accept-side pins for every measured shape pg genuinely opens and
for mongo's untouched multi-host form.

Claude-Session: https://claude.ai/code/session_01225pUjnCKWqxcc1PeqKFUq

Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec.

106 hand-written doc(s) reference the affected code — list omitted above 15 rows. Re-derive: node scripts/docs-audit/affected-docs.mjs --json origin/main.

7 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added documentation Improvements or additions to documentation dependencies Pull requests that update a dependency file protocol:data tests tooling labels Aug 16, 2026
@os-zhuang
os-zhuang marked this pull request as ready for review August 16, 2026 16:14
@os-zhuang
os-zhuang added this pull request to the merge queue Aug 16, 2026
Merged via the queue into main with commit 2ad91c3 Aug 16, 2026
28 checks passed
@os-zhuang
os-zhuang deleted the claude/issue-9091-postgres-url-pg-grammar branch August 16, 2026 16:32
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dependencies Pull requests that update a dependency file documentation Improvements or additions to documentation protocol:data size/l tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

A postgres config.url that pg itself cannot parse passes the authoring door — the shared credential-free URL check deliberately never parses

2 participants