@biffo/cli 0.258.0 → 0.258.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@biffo/cli",
3
- "version": "0.258.0",
3
+ "version": "0.258.1",
4
4
  "description": "Biffo project scaffolding CLI",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -79,6 +79,43 @@
79
79
  # the fingerprinted schema (see step 2 below) instead of rebuilding it under a
80
80
  # fresh identity every time. `BIFFO_PG_PORT`, `BIFFO_PG_DB`, and
81
81
  # `BIFFO_PG_CONTAINER` remain explicit overrides; only the *default* changed.
82
+ #
83
+ # ## The case that key does NOT cover: two runs from ONE checkout
84
+ #
85
+ # Deriving from `$REPO_ROOT` isolates two *checkouts*. It cannot isolate two
86
+ # *runs of the same checkout*, because it is deterministic on purpose -- that
87
+ # determinism is what makes reuse work. So a developer running the suite while
88
+ # `git push` fires the pre-push gate gets two concurrent sessions against ONE
89
+ # database, and neither is doing anything wrong: the gate is automatic, and the
90
+ # developer never chose to run two things at once.
91
+ #
92
+ # What that costs is not a lost race, it is a MISATTRIBUTED one. Measured on
93
+ # 2026-08-09: a push gate reported `test_lead_unsubscribe_pg.py` failing with a
94
+ # foreign-key violation, in a file the change under test never touched, while a
95
+ # full suite from the same worktree was concurrently deleting and re-seeding the
96
+ # rows it depends on. The gate was believed, the change was suspected, and the
97
+ # database had to be recreated before the red would clear.
98
+ #
99
+ # Per-test-file tenant namespaces (see `setup_pg`'s advisory-lock note) make ONE
100
+ # run internally parallel-safe. They do nothing here, because the colliding
101
+ # writers are the SAME file run twice.
102
+ #
103
+ # ## The fix, and why it is a clone rather than a lock
104
+ #
105
+ # The fingerprinted database is now a TEMPLATE, never handed out. Each run gets
106
+ # its own `..._r<key>` clone via `CREATE DATABASE ... TEMPLATE`, and the DSN
107
+ # points at that.
108
+ #
109
+ # This keeps the principle the port/name key already established -- make the
110
+ # value unique rather than make users take turns -- and it is affordable because
111
+ # a template clone is a file copy the server does itself: **0.10s for a 25 MB
112
+ # schema**, measured on the tabsii-platform lane, against the ~0.3s reuse path
113
+ # this script already advertises as fast. A `flock` would instead have cost the
114
+ # pushing developer the full runtime of whatever else was running.
115
+ #
116
+ # `BIFFO_PG_SHARED=1` opts out and returns the template directly, for the case
117
+ # that genuinely wants a stable name across invocations -- attaching a psql
118
+ # session to inspect what a failing run left behind.
82
119
 
83
120
  set -eu
84
121
 
@@ -102,7 +139,21 @@ HOST="${BIFFO_PG_HOST:-localhost}"
102
139
  PORT="${BIFFO_PG_PORT:-$_checkout_port}"
103
140
  USER_="${BIFFO_PG_USER:-postgres}"
104
141
  PASS="${BIFFO_PG_PASSWORD:-postgres}"
142
+ # The TEMPLATE: fingerprinted, rebuilt only when the schema inputs change, and
143
+ # never handed to a caller unless sharing is requested. An explicit BIFFO_PG_DB
144
+ # still names it, so that override keeps meaning what it always did.
105
145
  DB="${BIFFO_PG_DB:-biffo_test_$_checkout_suffix}"
146
+
147
+ # Opt out of per-run cloning (see the concurrency note above). Naming a database
148
+ # explicitly implies it: BIFFO_PG_DB is a request for THAT database.
149
+ SHARED="${BIFFO_PG_SHARED:-0}"
150
+ [ -n "${BIFFO_PG_DB:-}" ] && SHARED=1
151
+
152
+ # Minutes an abandoned clone survives before a later run reaps it. Generous on
153
+ # purpose: a clone with no connections may simply be between `--export` and the
154
+ # first test connecting, and dropping one out from under a caller is a worse
155
+ # failure than leaving a few megabytes on disk.
156
+ CLONE_TTL_MIN="${BIFFO_PG_CLONE_TTL_MIN:-240}"
106
157
  # Keyed the same way as PORT and for the same reason: the container is where
107
158
  # the port mapping actually lives (`docker run -p "$PORT:5432"`), so if the
108
159
  # container name stayed fixed while the port became per-checkout, a second
@@ -243,14 +294,76 @@ if ! psql_admin -c 'SELECT 1' >/dev/null 2>&1; then
243
294
  fi
244
295
 
245
296
  DSN="postgresql+asyncpg://$USER_:$PASS@$HOST:$PORT/$DB"
297
+
298
+ # What the caller is handed. Rewritten to a per-run clone below unless sharing
299
+ # was requested; `DSN` keeps naming the template, because that is what the
300
+ # alembic/DDL build steps must connect to.
301
+ RUN_DB="$DB"
302
+ RUN_DSN="$DSN"
303
+
304
+ # ── Per-run clone ────────────────────────────────────────────────────────────
305
+ #
306
+ # Called after the template is known-good, so a clone can never predate the
307
+ # schema it is supposed to carry.
308
+ clone_for_this_run() {
309
+ [ "$SHARED" -eq 1 ] && {
310
+ say "BIFFO_PG_SHARED - using the template $DB directly, NOT isolated from a concurrent run"
311
+ return 0
312
+ }
313
+
314
+ # Reap first, so a long-lived checkout does not accumulate clones forever.
315
+ # Only ones with no backends AND older than the TTL: `datconnlimit`-style
316
+ # liveness alone would drop a clone in the gap between `--export` and the
317
+ # first test connecting.
318
+ # Both ways this query can fail degrade to NOT reaping, which is the safe
319
+ # direction — it leaks disk rather than dropping a database out from under a
320
+ # live caller. `pg_stat_file` needs superuser (or pg_read_server_files), which
321
+ # the container has and a managed server may not; and a database in a custom
322
+ # tablespace is not under `base/`, so the file is missing and `missing_ok`
323
+ # returns NULL, which COALESCE turns into "not old enough".
324
+ _stale=$(psql_admin -tAc "
325
+ SELECT d.datname
326
+ FROM pg_database d
327
+ WHERE d.datname LIKE '${DB}_r%'
328
+ AND NOT EXISTS (SELECT 1 FROM pg_stat_activity a WHERE a.datname = d.datname)
329
+ AND COALESCE((pg_stat_file('base/' || d.oid || '/PG_VERSION', true)).modification,
330
+ now()) < now() - interval '$CLONE_TTL_MIN minutes'
331
+ " 2>/dev/null || true)
332
+ for _old in $_stale; do
333
+ psql_admin -c "DROP DATABASE IF EXISTS \"$_old\" WITH (FORCE)" >/dev/null 2>&1 || true
334
+ say "reaped abandoned clone $_old"
335
+ done
336
+
337
+ # $$ is this shell; the seconds make a second run in the same second (or a
338
+ # recycled pid) distinct. Short enough to read in a psql prompt.
339
+ _run_key=$(printf '%s%s' "$$" "$(date +%s)" | sha256sum | cut -c1-8)
340
+ RUN_DB="${DB}_r${_run_key}"
341
+
342
+ # WITH (FORCE) so a previous clone under the same name (only possible if the
343
+ # key collided) cannot wedge this run behind someone else's idle session.
344
+ psql_admin -c "DROP DATABASE IF EXISTS \"$RUN_DB\" WITH (FORCE)" >/dev/null 2>&1 || true
345
+ if ! psql_admin -c "CREATE DATABASE \"$RUN_DB\" TEMPLATE \"$DB\"" >/dev/null 2>&1; then
346
+ # The one failure mode worth naming: CREATE DATABASE ... TEMPLATE refuses
347
+ # while any session is connected to the template. That means something is
348
+ # using the template directly -- almost always a psql attached by hand, or a
349
+ # run started before this change with an exported DSN.
350
+ say "could not clone $DB - is something still connected to it?"
351
+ psql_admin -tAc "SELECT DISTINCT usename FROM pg_stat_activity WHERE datname = '$DB'" 2>/dev/null |
352
+ while read -r _u; do [ -n "$_u" ] && say " template in use by: $_u"; done
353
+ say "disconnect it, or set BIFFO_PG_SHARED=1 to use the template directly"
354
+ exit 1
355
+ fi
356
+ RUN_DSN="postgresql+asyncpg://$USER_:$PASS@$HOST:$PORT/$RUN_DB"
357
+ say "cloned $DB -> $RUN_DB (isolated from any concurrent run)"
358
+ }
246
359
  emit() {
247
360
  if [ "$EXPORT" -eq 1 ]; then
248
361
  # Both names, deliberately (tabsii-platform#755): whichever a consumer
249
362
  # reads, an `eval` of this line alone is enough -- see the Usage note above.
250
- echo "export BIFFO_TEST_PG_DSN='$DSN'"
251
- echo "export TABSII_TEST_PG_DSN='$DSN'"
363
+ echo "export BIFFO_TEST_PG_DSN='$RUN_DSN'"
364
+ echo "export TABSII_TEST_PG_DSN='$RUN_DSN'"
252
365
  else
253
- echo "$DSN"
366
+ echo "$RUN_DSN"
254
367
  fi
255
368
  }
256
369
 
@@ -277,6 +390,7 @@ fi
277
390
 
278
391
  if [ -n "$HAVE" ] && [ "$HAVE" = "$WANT" ]; then
279
392
  say "schema is current, reusing $DB"
393
+ clone_for_this_run
280
394
  emit
281
395
  exit 0
282
396
  fi
@@ -285,6 +399,13 @@ fi
285
399
 
286
400
  # --- 3. rebuild the way the app and CI do ------------------------------------
287
401
  say "rebuilding $DB"
402
+ # Every existing clone carries the OLD schema, so they are stale by definition
403
+ # the moment the template is rebuilt. Dropping them here is also what makes
404
+ # `CREATE DATABASE ... TEMPLATE` possible afterwards without waiting out the TTL.
405
+ for _c in $(psql_admin -tAc "SELECT datname FROM pg_database WHERE datname LIKE '${DB}_r%'" 2>/dev/null || true); do
406
+ psql_admin -c "DROP DATABASE IF EXISTS \"$_c\" WITH (FORCE)" >/dev/null 2>&1 || true
407
+ say "dropped clone $_c of the previous schema"
408
+ done
288
409
  psql_admin -c "DROP DATABASE IF EXISTS $DB WITH (FORCE)" >/dev/null
289
410
  psql_admin -c "CREATE DATABASE $DB" >/dev/null
290
411
 
@@ -331,5 +452,10 @@ psql_db \
331
452
  -c "TRUNCATE biffo_pg_test_fingerprint" \
332
453
  -c "INSERT INTO biffo_pg_test_fingerprint (value) VALUES ('$WANT')" >/dev/null
333
454
 
455
+ # Only now, with the fingerprint recorded against a schema that passed the
456
+ # check above, is the template fit to copy. Cloning earlier would hand out a
457
+ # half-built database and record the failure against whoever ran next.
458
+ clone_for_this_run
459
+
334
460
  say "ready"
335
461
  emit