Skip to content

feat(protect): template typecheck + NestJS adapter + rule-sync hardening#85

Merged
patchstackdave merged 4 commits into
mainfrom
feat/protect-hardening-batch
Jul 15, 2026
Merged

feat(protect): template typecheck + NestJS adapter + rule-sync hardening#85
patchstackdave merged 4 commits into
mainfrom
feat/protect-hardening-batch

Conversation

@patchstackdave

Copy link
Copy Markdown
Contributor

Three independent hardening items, one clean commit each (DNS-rebinding resistance is a separate follow-up PR).

1. Template-typecheck harness (test(protect))

Scaffolded templates are excluded from connect's tsconfig, so a template type bug ships silently — a broken install has slipped out this way before. scripts/typecheck-templates.mjs assembles each template in a scratch dir with the real @patchstack/connect/protect declarations, a stub rules JSON, and loose framework-type shims, then runs tsc --noEmit. Chained into npm run typecheck (so CI + prepublish cover it). Verified it fails on the exact bug class it targets (a string-typed mode) and passes on all 6 templates.

2. NestJS adapter (feat(protect))

NestJS on the default Express platform accepts app.use(...) in the bootstrap, so it wires the same way Express/Fastify do — anchored on NestFactory.create(...), reusing the generic guard (no Nest module/decorators). Extracted the shared register-into-app pattern (three adapters now use it) into install/register.ts + install/find-app.ts; Express/Fastify are now thin specs (behavior unchanged, their tests untouched and green).

3. Rule-sync hardening (feat(protect))

  • Conditional fetch: persist the ETag with the cached bundle (envelope { bundle, etag }), send If-None-Match, reuse the bundle on 304. Dormant until the rules API emits an ETag — with no ETag it behaves exactly as today (full fetch). Defensive against Responses without a headers object.
  • Jitter: randomly shorten the effective TTL by ≤10% so long-lived clients don't revalidate in lockstep (downward-only, preserving "past TTL ⇒ refetch").
  • Pluggable cache: ruleCache option for runtimes without a filesystem (Workers/Deno); disk stays default; legacy bare-bundle cache files still read.

+10 tests. 462 pass, typecheck (incl. the new template check) + build clean.

AGENT-INSTALL.md changed (NestJS added) → field-test before publishing.

patchstackdave and others added 3 commits July 15, 2026 18:16
Templates are excluded from connect's tsconfig (they import framework + JSON files that only exist
after scaffolding), so a template type bug ships silently — a broken install has slipped out this
way before. scripts/typecheck-templates.mjs assembles each template in a scratch dir with the real
@patchstack/connect/protect declarations, a stub rules JSON, and loose framework-type shims, then
runs `tsc --noEmit`. Chained into `npm run typecheck` so CI + prepublish cover it. Verified it fails
on the exact bug class it targets (a string-typed `mode`) and passes on all 6 current templates.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NestJS on the default Express platform accepts `app.use(...)` middleware in the bootstrap, so the
guard wires the same way Express/Fastify do — no Nest module/decorators needed. Anchored on
`NestFactory.create(...)`, reusing the framework-agnostic guard.

Extracted the register-into-app pattern (three adapters now share it) into install/register.ts
(wireRegister/verifyRegister + RegisterSpec) and the app-instance finder into install/find-app.ts;
Express + Fastify are now thin specs on top. hasDependency() moved to util.ts. No behavior change
for the existing adapters (their tests are unchanged and green).

Registered most-specific-first (… → astro → nestjs → fastify → express → generic). AGENT-INSTALL
lists NestJS. +2 tests, 458 total, typecheck (incl. template check) + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…able cache

Harden the per-site rule-delivery client:

- Conditional fetch: persist the response ETag alongside the cached bundle (envelope
  { bundle, etag }) and send If-None-Match on the next fetch; a 304 reuses the cached bundle with
  no re-download. Fully dormant until the rules API emits an ETag — with no ETag the client behaves
  exactly as before (a full fetch each refresh). Defensive against Responses without a headers
  object (some test mocks / non-standard fetch shims).
- Jitter: randomly shorten the effective cache TTL by up to 10% so many long-lived clients don't
  revalidate in lockstep. Downward-only, so "past TTL ⇒ refetch" still holds.
- Pluggable cache: new `ruleCache` option ({ read, write }, sync or async) for runtimes without a
  filesystem (Workers/Deno) to persist last-known-good in their own store; disk stays the default.
  The disk read accepts a legacy bare-bundle file for backward compatibility.

Applied to both the Pulse (siteUuid) and token clients; resolveRules threads the prior etag and
handles 304. +4 tests (rule-sync), 462 total, typecheck (incl. template check) + build clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@coderbuds

coderbuds Bot commented Jul 15, 2026

Copy link
Copy Markdown

Well-factored abstractions streamline adapter wiring and improve rules client caching.

🎯 Quality: 73% Good · 📦 Size: Extra Large — strongly consider breaking this down

🛡️ Standards: no pre-flight fit check ran for this change — wire assess-change-fit into your coding agents to catch size before opening.

📈 This month: Your 37th PR — above team average · Averaging Good

See how your team is trending →

@patchstackdave

Copy link
Copy Markdown
Contributor Author

/review

@patchstackdave patchstackdave merged commit b1b8b7f into main Jul 15, 2026
4 checks passed
@patchstackdave patchstackdave deleted the feat/protect-hardening-batch branch July 15, 2026 16:53
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.

2 participants