Skip to content

fix(tier 2–3): at-least-once became never when a lease bound was NaN - #370

Merged
sebyx07 merged 2 commits into
mainfrom
fix/sweep-17-tier23
Aug 26, 2026
Merged

fix(tier 2–3): at-least-once became never when a lease bound was NaN#370
sebyx07 merged 2 commits into
mainfrom
fix/sweep-17-tier23

Conversation

@sebyx07

@sebyx07 sebyx07 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

Tier 2–3 of the 17.0.0 sweep

Second of five, built from a main that already carries #364 — not rebased, not stacked. Every package here adopts finiteOption() / finiteCount() from @ultimat3/core rather than carrying its own copy.

The worst instance in the sweep: at-least-once became never

packages/jobs/src/worker-options.ts states it in its own header, and it was measured rather than reasoned:

Number(process.env.JOB_VISIBILITY_MS) on an unset variable is NaN; ?? guards only nullish, and Math.max/Math.min/Math.floor propagate it. So the value arrives at a lease deadline, a claim limit and a timer interval intact, and every comparison against it reads FALSE — measured on createMemoryDriver: visibleAt = at + NaN, the reclaim scan asks visibleAt <= at, and a job whose worker died is never claimable again. At-least-once becomes never, with no error and a row x jobs ls still prints as running.

Its quieter twin: slice(0, NaN) is [], so a concurrency: NaN worker claims nothing and reports healthy.

One place per subject

Package New file What was unbounded
jobs worker-options.ts every numeric knob createWorker accepts, the slot table included — a queue name is data, a slot count is a bound, and both arrive from the same deployment config
realtime sync-node-bounds.ts every option listenSyncNode accepts. maxConnections: NaN is a node that accepts without limit and says nothing — the AcceptBudget admits the whole herd
auth policy-numbers.ts the three policies defineAuth resolves, plus the options arriving on a call rather than through config: jwks, both OAuth legs, TOTP drift, the limiter's key cap

sync-node-bounds.ts is also a split — sync-node.ts was at the 500-line ceiling the filesize step enforces.

auth's refusal raises X_CONFIG_INVALID, which only answers with a row because #364 added one. Landing these in the other order would have shipped a window where the refusal existed and the status did not. That is what "tier order is the split order" buys.

packages/auth/src/auth-fixture.ts collapses three private copies of one credential-flow fixture — cheap KDF parameters, the shared password, the AuthError catcher. Three copies is three chances for one to drift from what the flow actually enforces. Not re-exported from the barrel; it is not public API.

The ratchets this slice is obliged to lower

Both fired on the first run, which is them working:

  • finite-bounds: 129 → 59 sites. X_FINITE_BOUND_PIN_STALE on seven packages the moment their repairs landed, because a pin above what the tree contains would let that many back in. Lowered with the --unpin the error itself names: action, auth, entity, http, jobs, query, realtime, all to zero — so all seven leave the table entirely, since a row claiming a debt of zero reads as a rule still in force over nothing.
  • proto-index: the realtime row deleted, its fix being in this slice.

scripts/finite-bounds.test.ts names every swept package individually rather than counting them, and a name may only ever be added to that list. It now carries tiers 0 through 3. Tier 4 and tier 5 add their own bands as they land.

Fixes #368 — the twin that could not be fixed until now

Two dbDrift() declarations, pinned to each other by a "keep in sync" comment, both built x db gen "add ${columnName}" — shell double quotes, so $(…) and backticks substitute, and a column named $(id) yields a fix: line that executes id when pasted. Byte-identical in shape to the hole closed in drift-findings.ts on #364.

It could not go in #364: the twins live in packages/db (tier 1) and packages/entity (tier 2), and a one-sided fix makes the "keep in sync" comment a lie. This slice is the first tree where both are editable in one commit.

db/errors.ts could not take the fix in place, and that was not foreseen: sql.ts imports identifierUnsafe from errors.ts, so importing the screen there would close an import cycle around the module whose evaluation calls registerErrorCodes(). packages/db/CLAUDE.md refuses exactly that in as many words. dbDrift(table, column) is public API shipped since 1.0 so its signature cannot change, which rules out the escape dependent-view.ts uses (hand errors.ts a finished string). The constructor moved to packages/db/src/drift-errors.ts instead — the pattern migration-errors.ts and invariant-errors.ts already follow. X_DB_DRIFT stays declared, titled and registered in errors.ts, and collectErrorCodes() still answers at: packages/db/src/errors.ts, so the manifest row is unchanged and @ultimat3/db's public surface is byte-identical.

The screen is promoted, not copied — a second copy of a security screen is the sql-literal-copies story, where three copies shipped and two were wrong the same way. It matters that the screen is not just identifier(): that function answers about SQL and accepts a backtick and a dollar sign (SAFE_IDENTIFIER even allows $ on its fast path), which are exactly the two characters a shell substitutes inside double quotes.

Every benign rendered literal is byte-identical, verified end-to-end across seven names (publish_at, id, orgId, a1, _x, o'brien, 63×x) against the formulas taken verbatim from HEAD, over all six affected sites. So none of the ~ten files quoting x db gen "add <name>" in packages/cli, packages/core, wiki/ and docs/ needed to move. Mutation-checked four ways, including rewording a benign literal — which goes red on both sides — and reverting entity's site alone, which goes red on the cross-package test asserting the two answer with the same text. "Keep in sync" is now asserted rather than asked.

Latent rather than live — neither twin has a runtime caller carrying a catalog-supplied name; the only caller is packages/cli/src/templates/entity.ts, which the CLI writes and never runs, with the literal 'id'.

Verification

  • bun run verify green on this slice alone, built from merged main.
  • Every changed source file has a changed test file beside it; every behavioural fix is mutation-proven.
  • No any, no as any, no @ts-expect-error, no biome-ignore.
  • No pin raised anywhere. Every pin-table edit lowers a count or deletes a row.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QVodtwtGAKyVC1SxvjZ6Mp


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…once became never when a lease bound was NaN

Measured on createMemoryDriver: Number(process.env.JOB_VISIBILITY_MS) on an
unset variable is NaN, ?? guards only nullish, and Math.max/Math.min/Math.floor
propagate it — so visibleAt = at + NaN, the reclaim scan asks visibleAt <= at,
and a job whose worker died is never claimable again. No error, and x jobs ls
still prints the row as running. Its twin: slice(0, NaN) is [], so a
concurrency: NaN worker claims nothing and reports healthy.

Every numeric option is now read and refused in one place per subject —
jobs/worker-options.ts, realtime/sync-node-bounds.ts, auth/policy-numbers.ts —
each delegating to @ultimat3/core's finiteOption/finiteCount. sync-node-bounds.ts
also takes sync-node.ts off the 500-line ceiling.

Fixes #368. Both dbDrift() twins spliced a column name into a shell-double-quoted
x db gen argument, where $(…) substitutes. The screen is promoted out of
drift-findings.ts to shellInertIdentifier() in db/sql.ts rather than copied:
identifier() answers about SQL and ACCEPTS a backtick and a dollar sign, so
reusing it alone would have shipped a green suite over a live hole — that
measurement is now a shipped test. db's constructor moved to drift-errors.ts
because errors.ts cannot import sql.ts without cycling around the module that
registers every code; the manifest row and the public surface are unchanged.
Every benign rendered literal is byte-identical across seven names, so no doc
quoting them moved.

The ratchets this slice repaired were lowered in the same commit, which is
X_FINITE_BOUND_PIN_STALE working: finite-bounds 129 -> 59 sites, seven packages
unpinned to zero; proto-index's realtime row deleted.

`bun run verify` green on this slice alone, built from merged main: 14 of 20
steps, 6 skipped.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QVodtwtGAKyVC1SxvjZ6Mp
@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

  • Run on-demand review

On-demand reviews are free for the next 25 days. After that, they cost $0.25 per reviewed file.

Or wait 50 minutes for your next included review.

View limit details

Limit details: You’ve used the included review currently available. Your 77 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Path: .coderabbit.yml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 70a18d99-b995-4a2c-b1b3-b1c13d834192

📥 Commits

Reviewing files that changed from the base of the PR and between 7c61971 and 11c438e.

📒 Files selected for processing (116)
  • CHANGELOG.md
  • packages/action/src/idempotency-memory.ts
  • packages/action/src/idempotency-postgres.ts
  • packages/action/src/idempotency-scope.test.ts
  • packages/auth/CLAUDE.md
  • packages/auth/src/auth-config.test.ts
  • packages/auth/src/auth-fixture.ts
  • packages/auth/src/auth-lockout.test.ts
  • packages/auth/src/auth.test.ts
  • packages/auth/src/auth.ts
  • packages/auth/src/errors.ts
  • packages/auth/src/jwks.test.ts
  • packages/auth/src/jwks.ts
  • packages/auth/src/kdf-gate.test.ts
  • packages/auth/src/kdf-gate.ts
  • packages/auth/src/mfa.test.ts
  • packages/auth/src/mfa.ts
  • packages/auth/src/oauth-cookie.test.ts
  • packages/auth/src/oauth-cookie.ts
  • packages/auth/src/oauth-discovery.test.ts
  • packages/auth/src/oauth-discovery.ts
  • packages/auth/src/oauth-exchange.ts
  • packages/auth/src/oauth-profile.ts
  • packages/auth/src/policy-numbers.ts
  • packages/auth/src/rate-limit.test.ts
  • packages/auth/src/rate-limit.ts
  • packages/auth/src/session.test.ts
  • packages/auth/src/session.ts
  • packages/auth/src/verify.test.ts
  • packages/auth/src/verify.ts
  • packages/db/CLAUDE.md
  • packages/db/README.md
  • packages/db/src/drift-errors.test.ts
  • packages/db/src/drift-errors.ts
  • packages/db/src/drift-findings.ts
  • packages/db/src/errors.ts
  • packages/db/src/index.ts
  • packages/db/src/sql.test.ts
  • packages/db/src/sql.ts
  • packages/entity/CLAUDE.md
  • packages/entity/src/errors.test.ts
  • packages/entity/src/errors.ts
  • packages/entity/src/plan.ts
  • packages/entity/src/query.ts
  • packages/http/CLAUDE.md
  • packages/http/src/config.test.ts
  • packages/http/src/config.ts
  • packages/http/src/errors.test.ts
  • packages/http/src/errors.ts
  • packages/http/src/rate-limit.test.ts
  • packages/http/src/rate-limit.ts
  • packages/http/src/webhook-verify.test.ts
  • packages/http/src/webhook-verify.ts
  • packages/jobs/CLAUDE.md
  • packages/jobs/src/backfill-ledger.ts
  • packages/jobs/src/driver-memory.ts
  • packages/jobs/src/driver-parity.test.ts
  • packages/jobs/src/driver-pg-ddl.ts
  • packages/jobs/src/driver-pg-sql.test.ts
  • packages/jobs/src/driver-pg-sql.ts
  • packages/jobs/src/driver-pg.ts
  • packages/jobs/src/driver.ts
  • packages/jobs/src/events-pg.ts
  • packages/jobs/src/events-ttl.test.ts
  • packages/jobs/src/events.ts
  • packages/jobs/src/index.ts
  • packages/jobs/src/limits-ceilings.test.ts
  • packages/jobs/src/limits.ts
  • packages/jobs/src/outbox.ts
  • packages/jobs/src/scheduler-pg.ts
  • packages/jobs/src/scheduler.ts
  • packages/jobs/src/steps.ts
  • packages/jobs/src/webhook.ts
  • packages/jobs/src/worker-bounds.test.ts
  • packages/jobs/src/worker-options.ts
  • packages/jobs/src/worker-slots.test.ts
  • packages/jobs/src/worker.ts
  • packages/query/CLAUDE.md
  • packages/query/src/read.ts
  • packages/query/src/search.test.ts
  • packages/query/src/search.ts
  • packages/realtime/CLAUDE.md
  • packages/realtime/src/change-buffer.ts
  • packages/realtime/src/changefeed.ts
  • packages/realtime/src/channel.ts
  • packages/realtime/src/client.ts
  • packages/realtime/src/live-definition.ts
  • packages/realtime/src/live-query.ts
  • packages/realtime/src/nats-lib-client-bounds.test.ts
  • packages/realtime/src/nats-lib-client.ts
  • packages/realtime/src/nats-transport.ts
  • packages/realtime/src/pg-replication.ts
  • packages/realtime/src/pg-wire.test.ts
  • packages/realtime/src/pg-wire.ts
  • packages/realtime/src/presence.ts
  • packages/realtime/src/socket-bounds.test.ts
  • packages/realtime/src/socket.ts
  • packages/realtime/src/subscription-book-bounds.test.ts
  • packages/realtime/src/subscription-book.ts
  • packages/realtime/src/sync-listen.ts
  • packages/realtime/src/sync-node-bounds.test.ts
  • packages/realtime/src/sync-node-bounds.ts
  • packages/realtime/src/sync-node.ts
  • packages/realtime/src/sync-upgrade.ts
  • packages/realtime/src/thundering-herd.test.ts
  • packages/realtime/src/thundering-herd.ts
  • packages/realtime/src/transport-env.ts
  • scripts/changelog-check.test.ts
  • scripts/changelog-check.ts
  • scripts/coverage-gate.ts
  • scripts/finite-bounds.test.ts
  • scripts/finite-bounds.ts
  • scripts/framework-tables.test.ts
  • scripts/lib/finite-bounds-pins.ts
  • scripts/lib/proto-index-pins.ts
  • wiki/Upgrading.md
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/sweep-17-tier23

Comment @coderabbitai help to get the list of available commands.

@sebyx07

sebyx07 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Aug 26, 2026

Copy link
Copy Markdown
⚠️ Action not completed

Review rate limited.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@developerz-ai

developerz-ai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

CI is green ✅ — no risk signals detected. Ready for maintainer merge.

🤖 Posted by developerz.ai — the maintainer agent, not a human.

@developerz-ai

developerz-ai Bot commented Aug 26, 2026

Copy link
Copy Markdown
Contributor

CI is green and no risk signals detected. Looks ready to merge — consider applying a ready-to-merge label.


🤖 AI review by developerz.ai[bot]

🤖 Posted by developerz.ai — the maintainer agent, not a human.

…ards that claimed more than they checked

The review round on this slice. Every behavioural fix is mutation-proven — the fix is
broken, the test is watched to go red, and restored.

`search().page({ first })` refused page ONE. The screen fired on the framework's own
defaults: `limit` defaults to 20, `first` has none, so `search({…}) + .page(input,
{ first: 10 })` was a 500 with nothing misdeclared. A screen that fires on its own
defaults is not a screen. It now refuses only when rows would actually be CUT, and when
the page fits `hasNextPage` is false by construction, so no cursor is minted that a
second call is guaranteed to throw on.

`trustedProxyHops: 0` read as a configured value and trusted nothing — `forwarded.ts`
returns undefined for `hops < 1`. The floor is 1, and the unreachable `?? 0` beside it is
removed rather than left reading as a live default.

`kdf.maxConcurrent: NaN` parked every `hashPassword` in a queue nothing releases, so
login stopped answering rather than shedding. The floor is 0 and not 1: a shipped test
uses `maxConcurrent: 0` as a deliberate zero-width gate.

Two guards claimed a reach they did not have.

`finite-bounds` matched `?? DEFAULT_TTL_MS[input.purpose]` with its pattern and then threw
it away in the numeric filter, because the declaration spans lines and the single-line
value capture reads `{`. A table of numbers is now its own set — kept apart from the
scalar set deliberately, since folding it in would report `o.opts ?? DEFAULT_OPTS`, an
object default, and a false report is how a rule gets switched off. A `\]` clause added to
the same regex the same day measured INERT — 59 sites with it and without, because an
identifier after a `]` is reached through `.` either way — so it is deleted rather than
left reading as a rule holding a line it is not holding.

`changelog-check` summed `[Unreleased]` into a total whose own row says "every major
section". Every PR landing a breaking change turned it red, and the repair was a number
the next release invalidates. It counts released sections now, and the documented command
reproduces it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@sebyx07

sebyx07 commented Aug 26, 2026

Copy link
Copy Markdown
Contributor Author

Review round: 6 defects, all fixed, all mutation-proven

The automated reviewer was rate-limited on this PR (77 reviews in 7 days → 1/hour) and the
maintainer bot then posted a false all-clear — "CI is green ✅ — no risk signals detected"
— on a PR carrying the defects below. A commissioned adversarial review found them instead.
CI being green is what let all six through, which is the point.

The one that mattered most

search().page(input, { first }) refused page ONE. This PR turned
{ first: 10 } over a limit: 20 read into X_INVARIANT → 500, and deleted the test named
"page one is served, and page TWO is refused rather than silently truncated".

The screen fired on the framework's own defaults: limit defaults to 20 (search.ts:43),
first has no default at all, and the two are written in two files with no type-level
connection. A screen that fires on its own defaults is not a screen.

The premise the screen was built on also does not hold — main did not drop rows
silently. paginate computes hasNextPage = scoped.length > first over a source main
capped at first + 1, so main was loud: it minted a cursor and refused page two. The
only real defect in main's onePage was the ordering (base.seek() sets totalized
id asc), which this PR fixed correctly and which is kept.

It now refuses only when rows would actually be cut, naming both edits. When the page
fits, hasNextPage is false by construction, so no cursor is handed back that a second call
is guaranteed to throw on — which answers the original complaint about main too.

The rest

# Defect Fix
2 trustedProxyHops: 0 read as configured and trusted nothing — forwarded.ts returns undefined for hops < 1 floor is 1; the unreachable ?? 0 removed, not left reading as a live default
3 kdf.maxConcurrent: NaN parked every hashPassword in a queue nothing releases — login stops answering rather than shedding screened; floor is 0, not 1 — a shipped test uses maxConcurrent: 0 as a deliberate zero-width gate, which disproved the first choice
4 a JSDoc block drifted onto the next symbol (rate-limit.ts), two stacked blocks with dead rationale (config.ts) reattached and merged
5 framework-tables.test.ts:97 justified itself with SQL_OUTBOX_TABLE, which this PR deletes comment states the rule that outlives it, and says the case is now synthetic
6 CHANGELOG.md [Unreleased] said "Nothing yet." while this slice and the merged tier-0/1 slice carried breaking changes written, both slices

Two guards that claimed more than they checked

Both found by testing the guard rather than reading it.

finite-bounds matched a table-keyed default and then threw it away. The regex accepted
?? DEFAULT_VERIFICATION_TTL_MS[input.purpose]; the numeric filter discarded it, because the
declaration spans lines and the single-line value capture reads {. So auth/src/verify.ts's
real defect would still have walked past a guard whose doc block said it was covered.
A table of numbers is now its own set — deliberately not folded into the scalar set, since
that would report o.opts ?? DEFAULT_OPTS, an object default, and a false report is how a
rule gets switched off.

A \] clause added to that same regex the same day measured INERT — 59 sites with it and
without, because an identifier following a ] is reached through . or ?. either way, so
the dot is always the deciding character. Deleted, along with the two comments calling it
load-bearing.

changelog-check summed [Unreleased] into a total whose own row says "every major
section".
Any PR landing a breaking change turned it red, and the repair was a number the
next release invalidates — a count that can only be right between merges. It counts released
sections now; the documented command was rewritten to reproduce it (35, against 38 whole-file);
regression test asserts both directions.

Verification

bun run verify14 of 20 passed, 6 skipped (drift, contract-diff, budgets, seo, i18n,
policy — all gate on app.config.ts and skip at the repo root). Baseline green.
bun run manifest → no change. bun run finite-bounds → 59 sites, no pin raised.

Routed onward rather than widened into this slice

  • packages/cli/src/dev-roles.ts:210 refuses hops > 16; config.ts allows 64. Two screens,
    one setting — cli is tier 5, so it lands in the tier-5 slice.
  • packages/http/src/response.ts:119 emits max-age=NaN, an unparseable directive a conforming
    cache ignores, so the response falls to heuristic caching. The layered fix belongs where the
    hint is declared (@ultimat3/render, tier 4) — throwing on the response hot path would turn a
    bad cache hint into a 500.
  • Finding 4 has no enforcement, and per axiom 3 that means it will come back. A rule for
    "a JSDoc block separated from the symbol it names" belongs in scripts/.

@sebyx07
sebyx07 merged commit 739eb4b into main Aug 26, 2026
38 checks passed
@sebyx07
sebyx07 deleted the fix/sweep-17-tier23 branch August 26, 2026 18:29
sebyx07 added a commit that referenced this pull request Aug 26, 2026
… 129 places (#378)

One sweep, five slices, in tier order: #364 (tiers 0–1), #370 (2–3), #374 (4),
#375 (5), #377 (the blind spots). `bun run finite-bounds` goes from 129 sites to
4, and both survivors are AUDITED pins carrying the sentence saying why
screening them would be worse, not unexamined debt.

THE DEFECT CLASS. `??` guards nullish and `NaN` is not nullish, so
`Number(process.env.X)` on an unset variable, a parseInt of a typo and an
untyped config value all walk past the default and land on the bound intact.
`Math.max`, `Math.min` and `Math.floor` are not validators either — all three
PROPAGATE NaN, and this repo was relying on all three as guards.

What that produced, each measured rather than reasoned about:

  a NaN token estimate did not bypass the AI budget, it POISONED it — after one
  such call a 5,000,000-token request passed a 1,000-token ceiling;
  `awaitActionable({ timeoutMs: NaN })` ran 835,462 polls in 3 seconds against a
  real browser, past ctx.signal, past the watchdog, past the job timeout;
  `syncAuthenticator({ ttlMs: NaN })` held a revoked session across a full year
  of clock advance;
  `randomToken(NaN)` returned "" — the framework's secret generator, producing
  no secret and reporting success;
  `generateRecoveryCodes(Infinity)` wedged the process on the enrolment path,
  and `NaN` enrolled a user with zero recovery codes;
  `configureLifecycle({ deadlineMs: NaN })` made a deploy drop in-flight
  requests and abandon close hooks on the first tick;
  an ISR page with a non-finite TTL was never fresh, so it regenerated on EVERY
  request;
  `chunk({ size })` and `embedBatched` were synchronous infinite loops.

THREE BREAKING ENTRIES, all the same shape: a numeric option that used to accept
NaN refuses it, at boot or at the call boundary rather than mid-request. An app
passing real numbers is unaffected. An app passing NaN was not working — the
bound it declared was not being enforced, and nothing said so. `0` stays legal
everywhere it means something: port 0 asks the OS for a free port, timeout 0 is
one look, seed 0 is a seed, maxAgeSeconds 0 is "revalidate every time".

THE RATCHET SHIPPED WITH THE SWEEP AND WAS WIDENED THREE TIMES BY DEFECTS THAT
WALKED PAST IT — an optional chain on the object, a default read out of a table
of numbers, and bare parameter defaults. All three were found the same way: by
TESTING the guard rather than reading it. One clause added along the way
measured inert and was deleted rather than left reading as a rule holding a line
it was not holding. Its non-vacuity guard also broke because the tree got
better: `total > 10` was calibrated at 129 sites, so it failed as the count
approached zero. It now asserts that every PINNED package still reports exactly
its pinned count.

Docs made true rather than restated: CLAUDE.md said `@ultimat3/notify` had never
been published and owed a hand publish before the next release run. True when
written; the 16.0.0 run published it, and the audit answers 31/31 attested — so
following that paragraph would have produced an E403.

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

db,entity: dbDrift's twin fix: lines still splice a column name into a shell-double-quoted x db gen argument

1 participant