Backmerge wave: Zealot → template (#75 Waves 1–2) - #81
Merged
Merged
Conversation
…able x-forwarded-for[0] Port of Zealot clientIp (ZLT-3195 / Zealot #1611): rightmost-trusted-hop XFF parsing with IPv6 /64 bucketing, replacing the client-supplied x-forwarded-for[0] that let any caller mint a fresh identity per request. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Port of Zealot incrementFixedWindows (Zealot #1611): a crash between the separate incr and expire calls could strand a TTL-less key that wedges an identity over-limit forever. Restructures middleware/rateLimit.ts into a directory (no consumers yet, zero import churn) and returns [count, pttl] per window. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backport from Zealot (ZLT-3008 squash + #1510): shared duck-typed P2002 and P2034 narrowing so insert-race call sites stop re-declaring the inline code check. Error middleware now uses isUniqueConstraintError. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Cherry-pick of packages/shared/src/utils/retry.ts from Zealot #1510 (stable since extraction): attempts/baseDelayMs/isRetryable matcher. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backport from Zealot #1510 (480fb4adc): a test-registered hook leaked across bun's single-process file boundary and the only removal API was clearHookRegistry(), which nukes production hooks too. Registrations are now recorded by name so unregisterDbHook(name) can filter just that handler out of each slot; clearHookRegistry also clears the registration map. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…API sockets Backport from Zealot #1682: publish() gated on pubsubEnabled, which is only set by initWebSocketPubSub() — called solely by the API server entry. A job handler emitting a websocket handoff delivered into the worker process's empty local registry and silently dropped. Publish now always goes through Redis (local delivery only as the Redis-failure fallback); subscribing stays the socket-holding process's job. Test seam: getRedisSub() in tests now duplicate()s the shared mock — a subscribed connection can only run subscriber commands, so sharing one mock instance between sub and general commands poisoned the whole suite. Includes Zealot's round-trip proof test: publish → Redis → subscriber → registered socket, plus wrong-channel isolation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backport of Zealot #1610 (ZLT-3167): a write that narrows its result with select/omit hands the after-write lifecycle hooks a partial row — a hidden changed column reads as a no-op, or the key-deriving field is stripped. - assertNoResultNarrowing at the six result-returning write seams - scripts/ci/rules/no-select-on-mutations.sh + brace-aware bun detector (lib/noSelectOnMutations.ts) + pass/fail fixtures; diff-mode vs base branch - repo scanned with the detector: no pre-existing violations Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…row caught
createToken omitted keyHash at the write and sendEmail's communicationLog
createManyAndReturn selected {id, idempotencyKey} — both now take the full
row and shape afterwards. Found at runtime by assertNoResultNarrowing; the
source detector missed them in the earlier sweep because the file list came
from a proxied find. Re-swept with git ls-files: clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Backport of Zealot #1510 (0ae3928f), reshaped for this repo: Zealot proxies its exported env object; here process.env IS the config (env.ts replaces it with the parsed schema), so the registry lives in @template/shared/utils and the proxy wraps process.env itself — installed by env.ts in test mode for apps/api and by a root/bunfig preload for package suites. - setEnvOverride / resetEnvOverrides / withEnv / wrapEnvWithOverrides - afterEach backstop in apps/api globalSetup + scripts/testEnvOverrides.ts (packages/email gets its own bunfig — bun test --cwd skips the root one) - no-test-env-mutation.sh + fixtures: bans env.X = / process.env.X = in tests across apps + packages - converted every mutating test: drain (8 sites), sendWebhook, encryption helpers + validation, email evaluateConditions + interpolate Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ct prisma in tests, awk no-prisma-maps Backport from Zealot main: - no-raw-transaction: bans .$transaction( outside packages/db/src/client.ts (raw txn bypasses db.txn's AsyncLocalStorage store, so mutationLifeCycle reissues each write into its own separate transaction) - no-direct-bulk-writes: bans createMany/updateMany in favor of the hook-firing *AndReturn variants; diff-mode vs base branch - no-direct-prisma-in-tests: factories-only in tests, diff-mode so the ~70 pre-existing sites converge as files are touched; packages/db/src/test/** exempt (db-layer seam tests). New jobOutboxFactory + drain.test.ts converted (this PR touches it, so diff-mode applies to it) - no-prisma-maps: Zealot's model-block awk parser replaces the blunt grep — enum-value @Map (legacy stored strings) now allowed, field/table maps still banned; new pass/enum-map fixture Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…thout assign-in-expression) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Open
19 tasks
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…redis url test on the env override seam Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ate limiting to its own PR Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Executes the Wave 1 + Wave 2 items from #75 (the Zealot→template backmerge tracker), same mechanic as #70. One commit per item, then a merge of today's main and a scope cut per review on 2026-09-17.
Wave 1 — standalone
isUniqueConstraintError+isWriteConflictError(7d3c6b86): duck-typed P2002/P2034 narrowing inpackages/db/src/utils/; error middleware uses both (P2002 → 409 exists, P2034 → 409 write conflict, P2025 → 404).withRetry(2b55363c, cherry-picked from Zealot #1510): exponential backoff withisRetryablematcher,packages/shared/src/utils/retry.ts+ tests.unregisterDbHook(a4811ef5, Zealot #1510): registrations recorded by name so a single hook can be removed — previously the only removal API wasclearHookRegistry(), which nukes production hooks, so a test-registered hook leaked across bun's single-process suite unremovably. Coexists with main'sregisterDbInvariant/unregisterDbInvariantand bridged stores. +3 registry tests.fd22c5cc, Zealot #1682):publish()gated onpubsubEnabled, which only the API server entry ever set — a job handler emitting a websocket handoff delivered into the worker's empty local registry and silently vanished. Publish now always goes through Redis; subscribing remains the socket-holding process's job. Includes Zealot's round-trip proof test.getRedisSub()returned the same sharedRedisMockinstance as the general client, so the firstsubscribeput the whole suite's Redis into subscriber mode. The test subscriber is now aduplicate()of the shared mock.Wave 2 — CI rules (
scripts/ci/rules/**)no-select-on-mutations(Zealot #1610): brace-aware bun detector + rule (diff-mode vs base branch) + fixtures. A narrowed write result breaks the after-write hooks. The sweep fixed two real write sites:createTokenomittedkeyHashat the write, andsendEmail'scommunicationLog.createManyAndReturnselected{id, idempotencyKey}. Both now take the full row and shape afterwards. Runtime guard dropped per review — this is a code-shape rule and the CI script is the enforcement.no-test-env-mutation(2c69d03b, Zealot #1510): reshaped for this repo — hereprocess.envis the config (env.ts replaces it with the parsed schema), so the registry lives in@template/shared/utils(setEnvOverride/resetEnvOverrides/withEnv) and the proxy wrapsprocess.envitself: installed byenv.tsin test mode for apps/api, and by a root bunfig preload for package suites. GlobalafterEachbackstops clear overrides between tests. All mutating test files converted, including main's newerredis/client.test.tsand the mid-drain shutdown test. The CI rule bansenv.X = …/process.env.X = …in tests across apps + packages.no-raw-transaction,no-direct-bulk-writes,no-direct-prisma-in-tests, awkno-prisma-maps(30ddd73e):$transactionbanned outsidepackages/db/src/client.ts. Repo scan: clean.createMany/updateManybanned in favor of the hook-firing*AndReturnvariants. Repo scan: clean.packages/db/src/test/**exempt. NewjobOutboxFactory+ drain.test.ts converted.no-prisma-mapsupgraded to Zealot's model-block awk parser: enum-value@mapallowed, field/table maps still banned.Moved out (2026-09-17 review)
clientIptrusted-hop util and theincrementFixedWindowsLua window are pulled from this PR and land with the full port of Zealot's rule-composition limiter (identity + context buckets, Retry-After, fail-open) in a dedicated PR.middleware/rateLimit.tsis back to main's state here.data.*rule vocabulary were dropped in the merge; main's versions stand.Validation (after merging main at
4da32f00)generate:sdkin a fresh worktree — staleness, unrelated)bun run test: permissions 81, db 305, email 474, api 1102 — 0 failscripts/ci/rules/*.shpass on the merged treeCloses the Wave 1 + Wave 2 checklists on #75.
🤖 Generated with Claude Code