Commit 7e74af3
Fixes #17621
Clause-②: no
#17175 compiled one non-raising catalog presence arm per dialect family
in `packages/metadata-protocol/src/migrations/read-probe.ts` and
executed two of the three: SQLite end to end through a real `SqlDriver`,
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, no `pg` dependency, and its CI step supplied
`OS_TEST_MYSQL_URL` alone while filtering vitest to `live-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 `catch`
that exists for the expected miss swallows it, and a stored-row data
repair silently becomes a no-op. Whether `to_regclass` answers **zero
rows** rather than raising is a claim about PostgreSQL, not about this
repo's string concatenation.
## The three changes
| # | change | note |
|:--|:--|:--|
| 1 | `pg` devDependency on `packages/metadata-protocol` | a lockfile
move — measured below |
| 2 | `src/migrations/seed-tenancy-backfill.live-postgres.test.ts` |
sibling of the live-MySQL file, in its own derived schema |
| 3 | `OS_TEST_POSTGRES_URL` + a second vitest substring on that CI step
| wiring: the `postgres:16` service is already up in that job |
Two riders, both consequences of the above rather than scope:
- `@types/pg` devDependency — `pg` ships no typings, and this package's
`typecheck` target reaches every `*.test.ts` by rule, so without it `tsc
--noEmit` fails `TS7016`. **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.0` and re-running `pnpm install` produced a
**three-line** lockfile diff, and the three lines are the importer
entry:
```
+ pg:
+ specifier: ^8.22.0
+ version: 8.22.0
```
Zero new packages, zero new transitives: `pg@8.22.0` was already
resolved for `packages/drivers/driver-sql`, which declares it as an
**optional peerDependency** (`pg`, `mysql2`, `tedious` under
`peerDependenciesMeta.optional`). The stop condition as triage set it —
`pg` pulling something unexpected into the install graph — did not fire.
The one package that **is** new is `@types/pg`, named above; it is not
pulled by `pg`, 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
expectation
The seat's expectation was `skip-changeset`, explicitly flagged as an
expectation and not a reading. Taken:
- **`dist/` is byte-untouched.** Built the package, grepped `dist/` for
this change's markers (`os17621_case`, `live-postgres`,
`toPgPlaceholders`): **zero hits**, against a positive control
(`readTablePresence`) that hits `dist/index.js` and `dist/index.cjs`.
Built entry typings are **byte-identical** across the change —
`dist/index.d.ts` sha256
`bace0e9e373ceef9f8150582b7e9d8ff3414d7358311c82058c005f3135c0360` from
a build of BASE sources and from a build of the branch alike.
- **But the published tarball does change.** `npm pack` ships 27 files,
and `package.json` is one of them (1.9 kB). The packed manifest now
carries `"pg": "^8.22.0"` and `"@types/pg": "^8.15.6"` under
`devDependencies` — verified by extracting `package/package.json` out of
the real tarball.
`AGENTS.md`: `skip-changeset` is for a diff that **publishes nothing
from any released package**. `@objectstack/metadata-protocol` is
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`'s
`repository.directory` correction took a changeset because "the value
ships inside the tarball", and `driver-sql` graded a test-only PR
`patch` rather than `skip-changeset`. So: **`patch`**, ⛔ not
`skip-changeset`, and `Check Changeset` should report `success`, not
`skipped`.
## The point of the card: the arm is EXECUTED, in both directions
⛔ No `--project` narrowing anywhere — the package declares no vitest
projects at all (no `projects` key, 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:
```
✓ is pointed at a real PostgreSQL, in this file's own schema — without this the run proves nothing
✓ every statement the migration builds PARSES and runs on PostgreSQL
✓ [#17621] the catalog presence probe ANSWERS on PostgreSQL — both directions, on the live server
✓ [#17621] the scope is the SEARCH PATH — a same-named relation off it is not this one
✓ [#17621] the arm's QUOTED argument matches exactly — a case-folded neighbour is not this table
✓ repairs the split end to end, and reports the already-minted duplicates
✓ is idempotent — a second run finds no split
Test Files 1 passed (1) Tests 7 passed (7)
[#17621] live PostgreSQL 16.13 ... search_path=os_lv_seed_tenancy_backfill_live_postgre_ae7a6c870c26
```
**Without a URL** the same file **skips** — reported as skipped, never
as passed:
```
Test Files 1 skipped (1) Tests 7 skipped (7) exit 0
```
**With `OS_EXPECT_LIVE_DIALECT_MATRIX=1` and no URL** it is a named red,
so a dropped `env:` line cannot return the seam to zero coverage
quietly:
```
× #17621 live PostgreSQL > OS_TEST_POSTGRES_URL must be set — this runner declared it provisioned a server
Test Files 1 failed (1) Tests 1 failed | 7 skipped (8) exit 1
```
### Ablation — the suite can fail
Committed the fix first, then re-compiled the Postgres arm to MySQL's
`DATABASE()` form in `read-probe.ts`. On-disk proof before believing the
run: anchor `to_regclass` 3 occurrences → 2, injected `DATABASE()` 2 →
3, blob hash `fd66ad50…` → `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 to `src/`, so a
stale `dist` cannot manufacture a false green here the way it can for a
dependency resolved through `exports`.
Result: **6 of the 7 cases go red**, reporting
```
expected { verdict: 'unreadable', …(2) } to deeply equal { verdict: 'absent', probe: 'catalog' }
+ "detail": "function database() does not exist"
```
— 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 with `git checkout HEAD -- PATH` and
verified: blob hash back to `fd66ad50…`, `git diff HEAD` empty, `git
status --porcelain` clean.
## Verification
| what | reading |
|:--|:--|
| package suite, unnarrowed (`pnpm --filter
@objectstack/metadata-protocol test`, live PG present) | **177 passed /
2 skipped (179 files)**, **2538 passed / 12 skipped (2550 tests)**, exit
0. The 2 skipped files are the live-MySQL pair — no MySQL server in this
container. |
| tiers | none. This package declares no vitest projects, so there is no
tier split to report and no `--project` filter was used. |
| `pnpm --filter @objectstack/metadata-protocol typecheck` | exit 0.
Proved it really compiles the new file: `tsc --noEmit --listFiles` names
`seed-tenancy-backfill.live-postgres.test.ts` (1 hit), positive control
`read-probe.ts` present, negative control `packages/cli/src` absent, 741
files total. |
| CI filter widening, measured | `vitest list --filesOnly live-mysql`
selects **3** files; `... live-mysql live-postgres` selects the **same 3
plus** `seed-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/objectstack` | 100 derived commands, all run at the final
head `66dff5cb02` with `dist` present. **100 exit 0.** `--ran`
reconciliation: `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 json` | exit 0, **6671 files**,
0 errors, 0 warnings — the whole repo population, not a narrowing. |
| `check:nul-bytes` | red once, and fixed here: the editing tool
materialised the unit-separator escapes in `sequenceKeyHash` into raw
`0x1F` bytes. Rewritten as escape text, gate green (8510 files scanned),
and an independent `grep -naP` sweep over every changed file finds
nothing. |
| `check:live-db-isolation` | PASS — the new file is listed among the
four live suites that derive their database, and it joins
`live-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 full `turbo run
build` over 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 attempts
| condition | result |
|:--|:--|
| (a) diff moves an `export` line, or changes the built entry `.d.ts` |
**negative, measured.** No `+`/`-` line in the diff contains `export`.
`dist/index.d.ts` byte-identical across a BASE-sources build and a
branch build (same sha256). |
| (b) `pg` reaches the **published** dependency graph | **negative,
measured.** The packed manifest's `dependencies` are unchanged (7
workspace links + `zod`); no `peerDependencies`, no
`optionalDependencies`. `pg` and `@types/pg` sit in `devDependencies`,
which no consumer installs. |
| (c) `scripts/pm/check-widening-tells.mjs --declaration no --diff FILE`
| exit 0 — **but read it as NOT MEASURED, not as a clean negative.** Its
own output: "0 judged against a declared surface, 5 NOT MEASURED … no
declared surface covers it" for all five files. (a) and (b) carry the
weight. |
## Rework — 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/main` was clean,
and every red job died at the same place:
```
ERR_PNPM_OUTDATED_LOCKFILE
specifiers in the lockfile don't match specifiers in package.json:
* 1 dependencies were removed: @types/pg@^8.15.6
```
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. 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`:
- **The restore check that missed it was `git status --porcelain --
PATH` 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, those two
readings point opposite ways.
- **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 — the same family as #17853, a verification
that passes because it never executed the thing in question. ⇒ a diff
touching `package.json` or `pnpm-lock.yaml` owes a clean-tree `pnpm
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**, and
`git diff --name-only` names `package.json` alone. Re-anchored at the
fixed head `66dff5cb02`: the live-PG suite is **7 passed (7)** again,
the skip direction **7 skipped (7)** at exit 0, `typecheck` exit 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,
and `eslint . --no-inline-config` 6671 files with 0 findings.
The changeset stays **`patch`**, and the reasoning holds harder now: the
packed `package.json` gains **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 from
`currentLiveMysqlDatabase()` — 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 in
`live-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 generalises `live-mysql-database.testkit.ts`. 承接者:
the next live-DB suite in `packages/metadata-protocol`.
- `noted, not filed:` `pr-automation.yml`'s documented `skip-changeset`
clobber is already fixed on this tree — both labeler steps now route
through `scripts/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 takes
`patch`. 承接者: 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](https://claude.ai/code)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent ed5a1e7 commit 7e74af3
6 files changed
Lines changed: 453 additions & 9 deletions
File tree
- .changeset
- .github/workflows
- packages/metadata-protocol
- src/migrations
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1407 | 1407 | | |
1408 | 1408 | | |
1409 | 1409 | | |
| 1410 | + | |
| 1411 | + | |
| 1412 | + | |
| 1413 | + | |
| 1414 | + | |
| 1415 | + | |
| 1416 | + | |
| 1417 | + | |
| 1418 | + | |
| 1419 | + | |
| 1420 | + | |
| 1421 | + | |
| 1422 | + | |
| 1423 | + | |
| 1424 | + | |
1410 | 1425 | | |
1411 | 1426 | | |
1412 | 1427 | | |
1413 | | - | |
| 1428 | + | |
1414 | 1429 | | |
1415 | 1430 | | |
| 1431 | + | |
| 1432 | + | |
| 1433 | + | |
| 1434 | + | |
1416 | 1435 | | |
1417 | 1436 | | |
1418 | | - | |
| 1437 | + | |
| 1438 | + | |
1419 | 1439 | | |
1420 | 1440 | | |
1421 | | - | |
| 1441 | + | |
1422 | 1442 | | |
1423 | 1443 | | |
1424 | 1444 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
49 | 49 | | |
50 | 50 | | |
51 | 51 | | |
| 52 | + | |
52 | 53 | | |
| 54 | + | |
53 | 55 | | |
54 | 56 | | |
55 | 57 | | |
| |||
Lines changed: 7 additions & 6 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
26 | 26 | | |
27 | 27 | | |
28 | 28 | | |
29 | | - | |
| 29 | + | |
| 30 | + | |
30 | 31 | | |
31 | 32 | | |
32 | | - | |
33 | | - | |
34 | | - | |
35 | | - | |
36 | | - | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
37 | 38 | | |
38 | 39 | | |
39 | 40 | | |
| |||
0 commit comments