feat(spec): declare the browser boundary — a browser export condition excluding the pg-grammar arm (#11072) - #11098
Conversation
…xcluding the pg-grammar arm (#11072) Option A per the maintainer's 2026-08-22 ruling: the five entries whose graph reaches the driver-config validators (., ./data, ./system, ./kernel, ./cloud) gain a browser export condition pointing at dist/browser/ bundles in which the postgres url refinement's pg-grammar arm (pg-connection-string, whose parse statically resolves require('fs')) is swapped for a dependency-free twin. Node-side resolution and behaviour are unchanged. check:browser-reachable-entries grows a second rule: every bundle a browser bundler resolves (browser-conditioned or not) must link no Node builtin and no server-only package, with a positive control on the Node side. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsbKEG4LtERSLaDrbehM3e
#11072) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01MsbKEG4LtERSLaDrbehM3e
📓 Docs Drift CheckThis PR changes 1 package(s): 1 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
What this run could not see
Coarse fallback — 126 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin fc1cb42a10cc8bc7d10dc55971b8bc5fc008742c && git checkout fc1cb42a10cc8bc7d10dc55971b8bc5fc008742c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin ab47f6974a275586355f06abd83a0975b8637d12 f975d0211a803cc919f3e2291090f559d01a2a3e && git checkout -B drift-repro ab47f6974a275586355f06abd83a0975b8637d12 && git merge --no-ff f975d0211a803cc919f3e2291090f559d01a2a3e
node scripts/docs-audit/affected-docs.mjs --json ab47f6974a275586355f06abd83a0975b8637d12
|
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 32586871687 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
Fixes #11072
What this does — Option A, per the maintainer's 2026-08-22 ruling on the card
@objectstack/specdeclares its browser boundary in theexportsmap. The five entries whose module graph reaches the driver-config validators —.,./data,./system,./kernel,./cloud— gain abrowserexport condition (both format halves, condition first in each entry object) pointing at a second build underdist/browser/**in which the postgresurlrefinement's pg-grammar arm is excluded.pg-connection-string— the parserpgitself uses, deliberately (standing #8082/#8337 grounds; Option C stays rejected) — statically resolves a filesystem require, so any browser bundler whose client graph reached one of these entries failed on an unresolvablefs(objectui's docs site, red on itsmain).Re-measured on this base (
4257e4e), not carried from the card: the card (at3e26359) counted six poisoned entries; on this base./sharedno longer links the parser (full-file scan of both format halves, not head-of-file), so the poisoned set — and therefore the browser-conditioned set — is these five. Single call site re-confirmed:packages/spec/src/data/driver/postgres.zod.ts. Zero pre-existingbrowserconditions confirmed.Mechanism
src/data/driver/pg-url-grammar.server.ts/pg-url-grammar.browser.ts): the server twin carries the onepg-connection-stringimport and the full A postgresconfig.urlthatpgitself cannot parse passes the authoring door — the shared credential-free URL check deliberately never parses #9091 grammar refusals, verbatim; the browser twin answers no findings.postgres.zod.tskeeps the shape-only pre-parse checks (unix-socket short-circuit, fs-reading?sslcert=/?sslkey=/?sslrootcert=refusal) in every build — exactly the degradation the ruling prescribes.tsup.config.ts): a second JS pass emits the five entries todist/browser/**with an esbuild resolve hook (swapServerOnlyGrammarArm) that swaps the seam specifier for the browser twin. Keyed to the one audited seam, NOT a blanket alias of the package — a future direct import of the parser lands in the browser bundles and the gate refuses it at this producer instead of silently degrading it. The DTS pass and the Node JS outputs are untouched;browser.typesreuses the main build's declarations (identical public API,check:api-surfaceunchanged). Naming note: the seam was first named.node.tsand tsup's built-in native-addon handling intercepts any specifier ending.node— hence.server.browser; every existingimport/requirecondition points at the same files; the full DSN refusal still runs for Node consumers — held by the existingpostgres.test.tspins (all green) plus a newpg-url-grammar.test.tspinning both twins' contract (server refuses multi-host / non-numeric port / scheme-less; browser answers no findings for the same values). Option B (server-only publish entry) not taken — rejected in the ruling.check:browser-reachable-entries, second rule): every bundle a browser bundler resolves — browser-conditioned or not — must link no Node builtin and no declared server-only package. A subpath without abrowsercondition is judged on its ordinary bundles, so the NEXT server-only dependency added to any entry reds this package's own CI instead of a downstream bundler hours later. Positive control: the scan must keep finding server-only links on the Node side of the browser-conditioned subpaths (currently 10 = 5 entries × 2 formats); zero is a refusal, never a silent green.Verification (all at head
f975d021, the tree this PR ships)pnpm --filter @objectstack/spec test— verdict line:Tests 11066 passed (11066)(416 files; includes every A postgresconfig.urlthatpgitself cannot parse passes the authoring door — the shared credential-free URL check deliberately never parses #9091 postgres pin).pnpm --filter @objectstack/spec typecheck— green; itscheck:test-typecheckverdict line reports the spec test-typecheck ledger held (55 files / 263 errors, unchanged).pnpm --filter @objectstack/spec build && check:generated— verdict line:All 14 generated artifacts are up to date(api-surface regenerated nothing: the export surface is byte-unchanged).check:browser-reachable-entries— self-test 25/25 including the new [seam→spec]@objectstack/spec@17.1.0statically importspg-connection-stringfrom six of its sixteen entry points, with nobrowserexport condition — objectui's site build is red onmain#11072 cases, then the real audit:44 bundle(s) scanned … 5 browser-conditioned subpath(s), 10 server-only link(s) on their Node side — positive control held.node:fsimport appended todist/browser/data/index.mjs→ exit 1, verdict line names./datalinking the Node-only specifiernode:fs; (leg B)./data's browser condition removed frompackage.jsonwhile its Node bundle still links the parser → exit 1, verdict lines name both format halves linkingpg-connection-string. No rebuild involved by design: the gate readsdistdirectly, so mutatingdist/package.jsonis the lever the gate actually sees.platform: browser, resolving@objectstack/specand@objectstack/spec/datathrough the exports map — bundles green, 3.27 MB output containing no parser import and nofsimport; the grammar-refusal text is absent while the shape-only fs-param refusal text is present (proof the browser condition, not the Node file, was resolved). Positive control: bundling the Node-conditioneddist/data/index.mjsunder the same platform fails with exactly the card's error — could not resolvefs.node scripts/pm/dispatch-gates.mjs, no hand-fed paths): all 25 path-matched families run at head — all exit 0 except two, both the same environmental precondition (66 of 67 workspace packages unbuilt in this fresh worktree, not a finding about this diff):check:doc-formula-expressionswent green after building its one input (@objectstack/formula) — verdict line9 @example(s) judged clean across 981 packages/spec/src files;check:dev-prereqs's own output saysThe workspace is not built — 1 unmet precondition, not a list of problems, and the half about this package passed (spec's build-input hash stamped by the build). Convention-triggered (new test file):check:query-options-erasure,check:type-check-coverage,check:engine-double-contract,check:where-matcherall green. Declared narrowing:check:type-check-debt --re-measure(full workspace) was not run locally — it requires the built closure of all 67 packages; the entries this diff can move are spec's, re-measured green insidetypecheckabove, and no other package's program inputs changed (spec's built declaration surface is proven unchanged by the api-surface artifact at this head). CI runs the full farm on this PR regardless.Scope and adjacents
@objectstack/specships 18 self-contained entry bundles with no shared chunks, so a consumer mixing entry points gets differentConnectorSchema/defineConnectorobjects (measured) #8133 (pm:on-hold) — its restart-when trigger fires on this PR: this diff touches thepackages/specbuild/publish pipeline (tsup config and the exports map). Named here so its re-grade fires; its cross-entry-identity / shared-chunks scope is not absorbed by this PR, and@objectstack/specships 18 self-contained entry bundles with no shared chunks, so a consumer mixing entry points gets differentConnectorSchema/defineConnectorobjects (measured) #8133 remains open.@objectstack/spec— additive resolution surface, zero Node-side movement (argued in the changeset body); not declared-breaking, so no ADR-0087 disposition marker is owed (check:adr-0087-registrationgreen).packages/spec/src/**touched; dispatched at the contract-review tier per the claim comment.Generated by Claude Code
Generated by Claude Code