Security-review finding F2: two concurrent acquire() calls on the same
MigrationLock instance could each check out a connection; the second
pg_advisory_lock would overwrite this.client, leaking the first locked
connection until session end.
acquire() now memoizes the in-flight acquire in acquireInFlight and
returns it on re-entry, so exactly one connection is checked out and no
locked connection leaks. The memo is cleared once the acquire settles.
tryAcquire()/release() paths unchanged.
Locked in by:
- static criterion test: acquireInFlight field, re-entry guard returns
the in-flight acquire, memo cleared on settle
- mutation probe: removing the re-entry guard fails the criterion
- real-stack probe: two concurrent acquire() calls on one instance leave
pool.totalCount at 1 (exactly one connection), the lock granted once,
nothing left after release; probe fails (hangs) on the pre-fix code
- CI job comment updated to reflect the re-entrancy criterion
MigrationLedger over the package-owned pg Pool: ensure() creates the
schema_migrations table (version text PRIMARY KEY, applied_at timestamptz
NOT NULL DEFAULT now()) with idempotent DDL; record() inserts an applied
migration with a parameterized, idempotent statement (ON CONFLICT DO
NOTHING — a rerun never double-applies); has()/applied() read the ledger
back in apply order. Re-exported from the driver boundary (src/index.ts)
so no other package needs the pg driver to touch migration state.
Review finding on PR #391: packages/database-postgres pinned pg@8.23.0 and
kysely@0.29.5, but the architecture doc's golden tuple (Technology-Stack
section 5.2 / section 7) pins pg@8.22.0 and Kysely@0.29.4. Reproducibility
requires the exact documented versions.
- packages/database-postgres/package.json: pg 8.23.0 -> 8.22.0,
kysely 0.29.5 -> 0.29.4, @types/pg 8.23.1 -> 8.21.0 (no 8.22.x of
@types/pg is published; 8.21.0 is the closest matching release, types
for the immediately preceding pg minor)
- pnpm-lock.yaml: regenerated with pnpm 11.23.0 (Node 24); the resolved
pg dependency tree is unchanged apart from the driver version itself
- tests/database-postgres-imports.test.mjs: exact-pin assertions updated
to the corrected versions, with a comment noting the @types/pg choice
- packages/database-postgres (@personal-blog/database-postgres): the single
workspace package allowed to import the PostgreSQL driver — declares pg and
kysely as exact dependencies (@types/pg for types) and its src/index.ts
imports and re-exports the driver pieces (Pool, Kysely, PostgresDialect) so
the isolation is real, not a placeholder
- pnpm-lock.yaml: importer for packages/database-postgres plus the resolved
pg/kysely dependency tree (frozen-lockfile install keeps working)
- .gitea/workflows/ci.yml: new database-postgres-imports job runs
tests/database-postgres-imports.test.mjs on every PR so the isolation
criterion gates merges
Adds root scripts so build, test and typecheck run from the workspace root:
- root package.json gains scripts: build (pnpm -r run build), test
(node --test on tests/**/*.test.mjs), typecheck (pnpm -r run typecheck)
- every workspace package (apps/server, packages/core,
extensions/example) gains build (tsc -p tsconfig.json) and typecheck
(tsc -p tsconfig.json --noEmit) scripts
- typescript 6.0.3 pinned as an exact root devDependency so the commands
run from a clean checkout; lockfile regenerated with pnpm 11.23.0
Verified from a clean state: pnpm install --frozen-lockfile passes,
pnpm build emits dist for all 3 packages, pnpm typecheck passes for all 3,
pnpm test runs tests/architecture-import.test.mjs 10/10 green, and CI's
pnpm -r list --depth -1 still lists all 4 workspace projects.
Closes#158
Adds ESM boundary declarations to every workspace package manifest
(apps/server, packages/core, extensions/example): "type": "module",
"main"/"types" entry points and an "exports" map (types + import
conditions) so each package exposes only its public root; adds
"type": "module" to the workspace root package.json so the workspace is
uniformly ESM. Placeholder src/index.ts files stay as empty ESM modules.
Verified: each package compiles under tsconfig.base.json (NodeNext) with
TypeScript 6.0.3 and emits ESM dist/index.js + dist/index.d.ts; Node
imports each package by name through its exports map and rejects deep
subpath imports (ERR_PACKAGE_PATH_NOT_EXPORTED); pnpm 11.23.0
install --frozen-lockfile passes with the lockfile unchanged.
Closes#156
Adds a strict base TypeScript config at the workspace root (ES2023 / NodeNext
/ strict family incl. noUncheckedIndexedAccess, exactOptionalPropertyTypes,
noImplicitOverride, useUnknownInCatchVariables, verbatimModuleSyntax per
Engineering-Standards) and gives every workspace package
(apps/server, packages/core, extensions/example) a tsconfig.json that extends
it. Each package gets a minimal src/index.ts placeholder so it compiles under
the base config (CJS-safe `export {}` until ESM boundaries land in T03).
Verified: every package typechecks and emits (js + d.ts + sourcemap) with the
pinned TypeScript 6.0.3; a strictness probe confirms the strict family fires.
Closes#155