test(metadata-protocol): execute the read-probe PostgreSQL arm on a live server (#17621) - #17890
Conversation
… live server #17175 compiled one catalog presence arm per dialect family and executed two of the three: SQLite end to end through a real SqlDriver, MySQL on the live server. The PostgreSQL arm was pinned character-for-character as TEXT and run nowhere — this package had no live-PG harness, no `pg` dependency, and the CI leg supplied `OS_TEST_MYSQL_URL` alone while filtering vitest to `live-mysql`. A text pin cannot close that gap. The failure the module is fenced against is an arm mis-compiled for one dialect: it raises, the `catch` that exists for the expected miss swallows it, and a data repair silently becomes a no-op. Only a server answers whether `to_regclass` comes back as ZERO ROWS rather than as an exception when the relation is not there. Three changes, the third of which is wiring rather than infrastructure — the postgres service is already up in that job for the driver-sql leg: - `pg` as a devDependency on `packages/metadata-protocol` (a lockfile move: the importer entry is the whole diff, `pg@8.22.0` is already resolved for driver-sql's optional peer); - `seed-tenancy-backfill.live-postgres.test.ts`, the sibling of the live-MySQL file, in its own derived schema on the search_path; - `OS_TEST_POSTGRES_URL` on that CI step and a second vitest substring. `read-probe.test.ts`'s docblock recorded the arm as NOT MEASURED anywhere; that sentence is now false, so it names where the arm runs instead. Claude-Session: https://claude.ai/code/session_01RuoNSXUbBoWHkNS4AknTrM Co-authored-by: Claude <noreply@anthropic.com>
…raw byte `check:nul-bytes` found three raw 0x1F bytes in the new live-PostgreSQL suite's `sequenceKeyHash`: the editing tool materialised the escape sequences into real bytes while the file was being written — the exact slip that gate's header records. Byte-identical at runtime; the live suite is green either way. Also adds the `patch` changeset, graded from the measurement rather than assumed: `dist/` is byte-untouched, but `package.json` ships in the tarball and now carries `pg` and `@types/pg`, so `skip-changeset` does not apply. Claude-Session: https://claude.ai/code/session_01RuoNSXUbBoWHkNS4AknTrM Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
…ile already pins `pnpm install --frozen-lockfile` refused on a clean tree — "specifiers in the lockfile don't match specifiers in package.json: 1 dependencies were removed: @types/pg@^8.15.6" — so every CI job that installs died before running anything. The lockfile was right and the manifest was wrong. `@types/pg` was added after the first commit on this branch; a later step of the `dist/index.d.ts` before/after measurement restored `packages/metadata-protocol` to HEAD, and HEAD at that moment predated the manifest edit. The pathspec did not cover `pnpm-lock.yaml`, so the lockfile kept the entry the manifest lost.⚠️ The restore check that missed it was `git status --porcelain -- PKG` coming back clean — which it was, because reverting to HEAD IS clean. A clean status proves the restore ran; it does not prove the restore preserved the state the run was supposed to leave behind. Against an edit made AFTER the commit being restored to, the two readings point opposite ways.⚠️ And nothing local could see the result: a warm `node_modules` never re-runs the frozen-lockfile check, so the defect existed only at install time on a clean tree while 100/100 derived gates and the full package suite stayed honestly green. A diff touching `package.json` or `pnpm-lock.yaml` owes a clean-tree `pnpm install --frozen-lockfile`; no amount of green elsewhere substitutes. Measured here: refused before the fix with CI's exact message, exit 0 after it, and the lockfile is byte-untouched — the mismatch closes from the manifest side. Claude-Session: https://claude.ai/code/session_01RuoNSXUbBoWHkNS4AknTrM Co-authored-by: Claude <noreply@anthropic.com>
席位落地复核 ——
|
Fixes #17621
Clause-②: no
#17175 compiled one non-raising catalog presence arm per dialect family in
packages/metadata-protocol/src/migrations/read-probe.tsand executed two of the three: SQLite end to end through a realSqlDriver, MySQL on the live server. The PostgreSQL arm —SELECT 1 WHERE to_regclass('"TABLE"') IS NOT NULL— was pinned character-for-character against all four knex client spellings and run nowhere: this package had no live-PG harness, nopgdependency, and its CI step suppliedOS_TEST_MYSQL_URLalone while filtering vitest tolive-mysql.A text pin cannot close that gap. The failure this module is fenced against is an arm mis-compiled for one dialect: it raises, the
catchthat exists for the expected miss swallows it, and a stored-row data repair silently becomes a no-op. Whetherto_regclassanswers zero rows rather than raising is a claim about PostgreSQL, not about this repo's string concatenation.The three changes
pgdevDependency onpackages/metadata-protocolsrc/migrations/seed-tenancy-backfill.live-postgres.test.tsOS_TEST_POSTGRES_URL+ a second vitest substring on that CI steppostgres:16service is already up in that jobTwo riders, both consequences of the above rather than scope:
@types/pgdevDependency —pgships no typings, and this package'stypechecktarget reaches every*.test.tsby rule, so without ittsc --noEmitfailsTS7016. Measured: it adds exactly one lockfile package entry; its three dependencies (@types/node,pg-protocol,pg-types) were already resolved. Dev-only, types-only, no runtime code.read-probe.test.ts's docblock recorded the Postgres arm as "NOT MEASURED against a live server anywhere", naming the three reasons this PR removes. That sentence is now false, so it names where each arm runs instead.Was (1) only a lockfile move? — triage's stop condition
Yes. Adding
pg: ^8.22.0and re-runningpnpm installproduced a three-line lockfile diff, and the three lines are the importer entry:Zero new packages, zero new transitives:
pg@8.22.0was already resolved forpackages/drivers/driver-sql, which declares it as an optional peerDependency (pg,mysql2,tediousunderpeerDependenciesMeta.optional). The stop condition as triage set it —pgpulling something unexpected into the install graph — did not fire. The one package that is new is@types/pg, named above; it is not pulled bypg, it is required by change (2) plus the repo's own type-check-coverage rule, and it is reported here rather than folded in silently.Changeset:
patch, measured — this falsifies the dispatching seat's expectationThe seat's expectation was
skip-changeset, explicitly flagged as an expectation and not a reading. Taken:dist/is byte-untouched. Built the package, greppeddist/for this change's markers (os17621_case,live-postgres,toPgPlaceholders): zero hits, against a positive control (readTablePresence) that hitsdist/index.jsanddist/index.cjs. Built entry typings are byte-identical across the change —dist/index.d.tssha256bace0e9e373ceef9f8150582b7e9d8ff3414d7358311c82058c005f3135c0360from a build of BASE sources and from a build of the branch alike.npm packships 27 files, andpackage.jsonis one of them (1.9 kB). The packed manifest now carries"pg": "^8.22.0"and"@types/pg": "^8.15.6"underdevDependencies— verified by extractingpackage/package.jsonout of the real tarball.AGENTS.md:skip-changesetis for a diff that publishes nothing from any released package.@objectstack/metadata-protocolis released at 17.4.0, and this diff publishes two manifest lines. That is the same standard the repo's own precedents apply —trigger-schedule'srepository.directorycorrection took a changeset because "the value ships inside the tarball", anddriver-sqlgraded a test-only PRpatchrather thanskip-changeset. So:patch, ⛔ notskip-changeset, andCheck Changesetshould reportsuccess, notskipped.The point of the card: the arm is EXECUTED, in both directions
⛔ No
--projectnarrowing anywhere — the package declares no vitest projects at all (noprojectskey, no workspace config), so there is no project filter to drop a named file into.Against a live PostgreSQL 16.13 (
OS_TEST_POSTGRES_URL+OS_EXPECT_LIVE_DIALECT_MATRIX=1) — 7 of 7 executed, by name:Without a URL the same file skips — reported as skipped, never as passed:
With
OS_EXPECT_LIVE_DIALECT_MATRIX=1and no URL it is a named red, so a droppedenv:line cannot return the seam to zero coverage quietly:Ablation — the suite can fail
Committed the fix first, then re-compiled the Postgres arm to MySQL's
DATABASE()form inread-probe.ts. On-disk proof before believing the run: anchorto_regclass3 occurrences → 2, injectedDATABASE()2 → 3, blob hashfd66ad50…→86ea33de…. No rebuild was needed or performed, and that is itself the resolution evidence: the subject is same-package./read-probe.js, which vitest resolves tosrc/, so a staledistcannot manufacture a false green here the way it can for a dependency resolved throughexports.Result: 6 of the 7 cases go red, reporting
— exactly the mis-compiled-arm shape the fence exists to keep out of
'absent'. The one case that stays green is the provisioning/non-vacuity check, which does not exercise the arm; that is the correct partition. Restored withgit checkout HEAD -- PATHand verified: blob hash back tofd66ad50…,git diff HEADempty,git status --porcelainclean.Verification
pnpm --filter @objectstack/metadata-protocol test, live PG present)--projectfilter was used.pnpm --filter @objectstack/metadata-protocol typechecktsc --noEmit --listFilesnamesseed-tenancy-backfill.live-postgres.test.ts(1 hit), positive controlread-probe.tspresent, negative controlpackages/cli/srcabsent, 741 files total.vitest list --filesOnly live-mysqlselects 3 files;... live-mysql live-postgresselects the same 3 plusseed-tenancy-backfill.live-postgres.test.ts. Multiple positionals are OR-ed and each stays a substring.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack66dff5cb02withdistpresent. 100 exit 0.--ranreconciliation:100 derived, 100 run, 0 NOT-MEASURED, 0 UNRUN— a derived zero, every line recorded its exit code. The command set re-derived at that head is identical to the one derived before the rework commit.eslint . --no-inline-config --format jsoncheck:nul-bytessequenceKeyHashinto raw0x1Fbytes. Rewritten as escape text, gate green (8510 files scanned), and an independentgrep -naPsweep over every changed file finds nothing.check:live-db-isolationlive-mysql-database.isolation.test.ts's auto-discovered population (9 cases green, distinctness now measured over 4 files rather than 3).Three gates first answered exit 3 — PREREQUISITE NOT MET in this fresh worktree (
check:dual-build-cjs-loads,check:lean-entry-closure,check:type-check-debt); a fullturbo run buildover the workspace (72/72 tasks) was run and all three were then measured green. ⛔ None of those exit-3s was read as a pass.Clause-②:
no— the three falsification attemptsexportline, or changes the built entry.d.ts+/-line in the diff containsexport.dist/index.d.tsbyte-identical across a BASE-sources build and a branch build (same sha256).pgreaches the published dependency graphdependenciesare unchanged (7 workspace links +zod); nopeerDependencies, nooptionalDependencies.pgand@types/pgsit indevDependencies, which no consumer installs.scripts/pm/check-widening-tells.mjs --declaration no --diff FILERework — the install-time defect this PR shipped red with, and the verification hole behind it
The first push was 24-of-35 red in CI while
origin/mainwas clean, and every red job died at the same place:The lockfile was right and the manifest was wrong.
@types/pgwas added after the first commit on this branch; a later step of thedist/index.d.tsbefore/after measurement restoredpackages/metadata-protocoltoHEAD, andHEADat that moment predated the manifest edit. The pathspec did not coverpnpm-lock.yaml, so the lockfile kept the entry the manifest lost. Fixed by putting the one line back — the lockfile is byte-untouched, the mismatch closes from the manifest side.Two things worth carrying, both about verification rather than about
pg:git status --porcelain -- PATHcoming back clean — which it was, because reverting toHEADis clean. A clean status proves the restore ran; it does not prove the restore preserved the state the run was supposed to leave behind. Against an edit made after the commit being restored to, those two readings point opposite ways.node_modulesnever re-runs the frozen-lockfile check, so the defect existed only at install time on a clean tree while 100/100 derived gates and the full package suite stayed honestly green — the same family as [finding] vitest 的 --project 过滤器落空即静默成功 —— 点名一个 integration 文件跑 --project unit,报它是通过的文件、执行零个用例,并把它从文件计数里减掉 #17853, a verification that passes because it never executed the thing in question. ⇒ a diff touchingpackage.jsonorpnpm-lock.yamlowes a clean-treepnpm install --frozen-lockfile, and no amount of green elsewhere substitutes.Measured on a genuinely clean tree (fresh worktree, no
node_modules): exit 1 before the fix with CI's exact message, exit 0 after it, andgit diff --name-onlynamespackage.jsonalone. Re-anchored at the fixed head66dff5cb02: the live-PG suite is 7 passed (7) again, the skip direction 7 skipped (7) at exit 0,typecheckexit 0, the package suite 176 passed / 3 skipped (179 files) and 2531 passed / 19 skipped (2550 tests) with no live servers, all 100 derived gates exit 0, andeslint . --no-inline-config6671 files with 0 findings.The changeset stays
patch, and the reasoning holds harder now: the packedpackage.jsongains two dependency lines rather than one.Authored by Claude Code — session
session_01RuoNSXUbBoWHkNS4AknTrM(https://claude.ai/code/session_01RuoNSXUbBoWHkNS4AknTrM). Attribution kept in prose here because this body was edited through raw REST, whose measured behaviour is to append its own footer block.Acceptance notes
noted, not filed:the new file's per-file schema comes fromcurrentLiveMysqlDatabase()— a MySQL-named resolver in a PostgreSQL suite. Deliberate, and the file's docblock says why: the derivation is dialect-neutral and already capped at PostgreSQL's 63-byte limit, and calling it by that name is what enrols the file inlive-mysql-database.isolation.test.ts's population, which is discovered by reading each test file for that exact call. A dialect-neutral alias would buy a better name at the price of this file's distinctness never being measured. Renaming the resolver repo-wide is mechanical and belongs to whoever adds the next live suite to this package or generaliseslive-mysql-database.testkit.ts. 承接者: the next live-DB suite inpackages/metadata-protocol.noted, not filed:pr-automation.yml's documentedskip-changesetclobber is already fixed on this tree — both labeler steps now route throughscripts/pr-labels.mjs(POST + targeted DELETE, no whole-set PUT), so the "apply the label after the push settles" precaution is stale advice rather than a live hazard. Moot for this PR, which takespatch. 承接者: whoever next reads that precaution in a dispatch.⛔ Draft, and left that way: landing is the PM seat's act. Not marked ready, auto-merge not armed.
Generated by Claude Code