Skip to content

Add support for rate limits in rules#2554

Open
dwwoelfel wants to merge 24 commits intomainfrom
rate-limit
Open

Add support for rate limits in rules#2554
dwwoelfel wants to merge 24 commits intomainfrom
rate-limit

Conversation

@dwwoelfel
Copy link
Copy Markdown
Contributor

@dwwoelfel dwwoelfel commented Apr 14, 2026

Adds support for rate limits in rules.

You define the rate limit configs inside of the permissions file with a "$rateLimits" key. And then you can use them by adding rateLimit.yourKey.limit(anyValue), where anyValue will probably be auth.id or request.ip.

I decided to put the rate limits in the perms file because it lets us validate that any usage of rateLimit.someConfig has the corresponding "$rateLimits" config. That will prevent any runtime errors. If we put them in multiple files, then it would be harder for us to ensure that they were in sync. The downside is that we can't create a system table called $rateLimits, but I think that's ok.

Here what the rules code looks like:

{
  "myNs": {
    "allow": {
      "create": "rateLimit.createMyNs.limit(auth.id)"
    }
  },
  "$rateLimits": {
    "createMyNs": {
      "limits": [{
        "capacity": 10,
        "refill": {
          "period": "1 hour"
        }
      }]
    }
  }
}

If you exceed the limit, we'll throw a custom rate-limit error that looks like this:

{
  "hint": {
    "retry-at": "2026-04-15T16:09:24.530786Z",
    "retry-after": 1,
    "remaining-tokens": 0
  },
  "traceId": "074d1463fc63c38ced71aceeda306c19",
  "name": "InstantAPIError",
  "status": 429,
  "body": {
    "type": "rate-limited",
    "message": "Your request exceeded the rate limit.",
    "hint": {
      "retry-at": "2026-04-15T16:09:24.530786Z",
      "retry-after": 1
    },
    "trace-id": "074d1463fc63c38ced71aceeda306c19"
  }
}

If you have some operation that consumes more than one token, limit takes an optional 2nd "tokens" arg for the amount of tokens, e.g. rateLimit.createMyNs.limit(auth.id, 10) will consume 10 tokens (or throw if fewer than 10 are available).

Notes on implementation

We use bucket4j for the rate limit buckets. It stores data in a hazelcast map which is also backed by postgres using a MapStore. Hazelcast gives us fast in-memory updates, but can still fetch the older entries from postgres when we exceed our memory budget.

The postgres store is cleaned out periodically, deleting anything that hasn't been updated in two days. We don't let you set a refill period longer than 1 day, so we can be certain that anything older than 2 days is stale.

We could put the MapStore in an s3 directory bucket instead of postgres, but I'm hoping postgres will be enough since that will be easier to manage.

Rate-limit keys

The rate-limit bucket is just a giant map. We hash all of the inputs with hmac-sha256 and then take the first 128 bits for the key. That should give us good collision resistance and make it impossible to pre-generate collisions. We put the key into a UUID format. That lets us use a little postgres trick--instead of storing the key as bytea, we can store it as a uuid. uuid has a fixed-length, so we save a byte per row.

We previously wrapped the key in a record, e.g.CreateMagicCodeKey. Now we just use a bare UUID and include a string for each key type in the hash. That saves us a little overhead in hazelcast. I updated the map name so that we don't have to worry about backwards compatibility (has the effect of a 1-time rate-limit reset).

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 14, 2026

Note

Currently processing new changes in this PR. This may take a few minutes, please wait...

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f4bc8c17-ae34-44bb-addf-d58005b2459a

📥 Commits

Reviewing files that changed from the base of the PR and between f2b8ce7 and a532d12.

📒 Files selected for processing (16)
  • client/packages/core/src/rulesTypes.ts
  • client/packages/core/src/utils/fetch.ts
  • client/sandbox/react-nextjs/pages/play/rate-limit.tsx
  • client/www/app/docs/rate-limits/page.md
  • client/www/data/docsNavigation.js
  • client/www/lib/permsJsonSchema.tsx
  • server/resources/config/staging.edn
  • server/src/instant/db/cel.clj
  • server/src/instant/db/instaql.clj
  • server/src/instant/model/rule.clj
  • server/src/instant/rate_limit.clj
  • server/src/instant/util/exception.clj
  • server/test/instant/db/instaql_test.clj
  • server/test/instant/db/transaction_test.clj
  • server/test/instant/model/rule_test.clj
  • server/test/instant/rate_limit_test.clj
📝 Walkthrough

Walkthrough

This PR introduces a complete rate-limiting system for InstantDB, including CEL expression support for rate-limit rules, a Hazelcast-backed persistence layer with PostgreSQL storage, HMAC-based bucket key hashing, periodic cleanup of stale entries, and a client-side testing playground.

Changes

Cohort / File(s) Summary
Client-side rate-limit playground
client/sandbox/react-nextjs/pages/play/rate-limit.tsx
New Next.js page with InstantDB schema, permissions editor (loads/persists superadmin rules via admin token), todo CRUD app, and spam-action buttons (query/transact loops) to test rate-limit triggering and error handling.
Bug fix
client/www/components/dash/Perms.tsx
Defensive optional chaining for error header rendering (errorRes?.in?.join('->')) to prevent crashes when errorRes.in is missing.
Database schema
server/resources/migrations/104_rate_limit_keys.up.sql, 104_rate_limit_keys.down.sql
New rate_limit_keys table (UUID key, bytea value, timestamps) with updated_at index and auto-updating trigger for efficient row expiration.
Core app initialization
server/src/instant/core.clj
Wired rate-limit/start in main initialization and rate-limit/stop in shutdown hook with tracer spans.
CEL expression support
server/src/instant/db/cel.clj
Added RateLimitBucket opaque type, rateLimit variable binding, limit()/tryLimit() member functions, rate-limit validator for $rateLimits references, and enhanced function-name extraction for rate-limit method calls.
Rule validation & CEL integration
server/src/instant/db/instaql.clj
Extended query context map to include :rules alongside :rule-wheres for rate-limit validation during queries.
Rate-limit rule validation
server/src/instant/model/rule.clj
Added rate-limit-validation-errors pass to validate $rateLimits config parsing, marked $rateLimits as reserved namespace, and updated CEL validation to receive full rules map.
Rate-limit engine
server/src/instant/rate_limit.clj
Core implementation: Hazelcast MapStore backed by Postgres, user-scoped rate-limit logic with HMAC-based bucket-key hashing, duration parsing, try-consume-user-rate-limit/consume-user-rate-limit functions, and periodic chime-scheduled sweep to delete stale rate_limit_keys rows.
Exception handling
server/src/instant/util/exception.clj
Added throw-permission-rate-limited! function; updated throw-permission-evaluation-failed! to detect and rethrow underlying ::rate-limited causes instead of wrapping.
Configuration
server/resources/config/dev.edn, prod.edn, staging.edn, server/src/instant/config.clj, config_edn.clj
Added :rate-limit-hmac-key config entry (encrypted), derived rate-limit-hmac-secret delay (SecretKeySpec for HmacSHA256), and extended config spec to validate the new key.
Serialization cleanup
server/src/instant/util/hazelcast.clj
Removed CreateMagicCodeKey/ConsumeMagicCodeKey record serializers and type IDs (replaced by direct UUID computation in rate_limit.clj).
Test updates
server/test/instant/model/rule_test.clj, instant/util/hazelcast_test.clj
Added rate-limit config validation test; removed magic-code-key roundtrip tests that depended on removed serializers.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant CEL as CEL Evaluator
    participant RateLimitBkt as RateLimitBucket
    participant Hazelcast
    participant Postgres
    participant Sweep as Periodic Sweep

    Client->>CEL: Evaluate permission rule with rateLimit.<config>.limit(key)
    CEL->>RateLimitBkt: Create RateLimitBucket for app/config
    RateLimitBkt->>Hazelcast: Request bucket for bucket-key (HMAC-hashed)<br/>via get-bucket-with-config
    Hazelcast->>Postgres: Load existing rate-limit-keys row<br/>(via MapStore.load)
    Postgres-->>Hazelcast: Return key row or null
    Hazelcast-->>RateLimitBkt: Return bucket (in-memory or<br/>reconstructed from Postgres)
    RateLimitBkt->>RateLimitBkt: tryLimit(): consume tokens<br/>or throw exception
    RateLimitBkt->>Hazelcast: Update bucket state
    Hazelcast->>Postgres: Persist bucket to rate_limit_keys<br/>(async MapStore.store)
    Postgres-->>Hazelcast: ACK
    Hazelcast-->>CEL: Success or RateLimitedException
    CEL-->>Client: Return permission result<br/>or rate-limited error with retry-after

    par Async Cleanup
        Sweep->>Postgres: Periodic: DELETE stale rate_limit_keys<br/>where updated_at < now - interval<br/>(skip-locked, bounded retries)
        Postgres-->>Sweep: Rows deleted
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

  • Streams #2257 — Both modify server-side rule validation infrastructure (server/src/instant/model/rule.clj) to extend reserved namespaces and validation plumbing, adding rate-limit validation logic alongside stream-related validation changes.
  • Puts rate limit in hazelcast (behind a flag) #2404 — Both directly modify the rate-limiting subsystem (server/src/instant/rate_limit.clj) and Hazelcast integration (serializers, bucket management), making them code-level companions for the same feature.

Suggested reviewers

  • stopachka
  • nezaj
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 45.45% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: adding rate limit support in the permissions/rules system.
Description check ✅ Passed The description clearly explains the rate limit feature, provides concrete examples of the schema and API, and details implementation decisions and trade-offs.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 rate-limit

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@github-actions
Copy link
Copy Markdown
Contributor

View Vercel preview at instant-www-js-rate-limit-jsv.vercel.app.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🧹 Nitpick comments (3)
server/resources/migrations/104_rate_limit_keys.down.sql (1)

1-1: Consider making the down migration idempotent.

Using DROP TABLE IF EXISTS makes rollback safer in partial/repair scenarios.

🧱 Suggested change
-drop table rate_limit_keys;
+drop table if exists rate_limit_keys;
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/resources/migrations/104_rate_limit_keys.down.sql` at line 1, The down
migration uses a hard DROP TABLE which can fail if the table is already absent;
update the statement in 104_rate_limit_keys.down.sql so the rollback is
idempotent by using a conditional drop (e.g., change the DROP TABLE for
rate_limit_keys to use IF EXISTS) so running the down migration repeatedly or
during partial/repair scenarios won't error.
client/sandbox/react-nextjs/pages/play/rate-limit.tsx (1)

141-147: Add a fallback when e.message is missing in formatError.

Line 146 can render undefined for unknown error shapes; fallback text would keep the output readable.

💡 Suggested tweak
 function formatError(label: string, e: any): string {
   const retryAfter = e?.hint?.['retry-after'];
   if (retryAfter != null) {
     return `${label}: Try again in ${retryAfter} seconds`;
   }
-  return `${label}: ${e.message}`;
+  return `${label}: ${e?.message ?? 'Unknown error'}`;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/sandbox/react-nextjs/pages/play/rate-limit.tsx` around lines 141 -
147, The formatError function can return "undefined" when e.message is absent;
update formatError to use a safe fallback for the error text (e.g., use
e?.message, then String(e) or a literal like "Unknown error" when that is
missing) so the returned string is always readable; modify the body of
formatError (the function named formatError and its usage of e.message) to
prefer e?.message ?? String(e) ?? 'Unknown error' (or equivalent) before
interpolating into the returned message.
server/src/instant/rate_limit.clj (1)

83-88: Inconsistent metric name in storeAll.

The storeAll method uses ::store as the metric/trace identifier instead of ::storeAll, which could cause confusion when analyzing metrics or traces.

♻️ Proposed fix
     (storeAll [_ m]
-      (sql/do-execute! ::store
+      (sql/do-execute! ::storeAll
                        (aurora/conn-pool :write)
                        ["insert into rate_limit_keys (key, value) values (unnest(?::bytea[]), unnest(?::bytea[])) on conflict (key) do update set value = excluded.value"
                         (with-meta (keys m) {:pgtype "bytea[]"})
                         (with-meta (vals m) {:pgtype "bytea[]"})]))))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/instant/rate_limit.clj` around lines 83 - 88, The metric/trace
identifier passed to sql/do-execute! inside the storeAll method is incorrectly
::store; update that call so the identifier matches the method by using
::storeAll (i.e., replace ::store with ::storeAll in the sql/do-execute!
invocation within the storeAll implementation) so metrics/traces correctly
reflect the storeAll operation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/www/components/dash/Perms.tsx`:
- Line 276: The rendered fallback string contains a leading space: change the
expression in the JSX from using the fallback string ' the rules' to 'the rules'
so the message in the Perms component ("There was an error in
{errorRes?.in?.join('->') || ' the rules'}") no longer produces "in  the rules";
update the fallback to 'the rules' inside the same JSX expression where
errorRes?.in?.join('->') is used.

In `@server/src/instant/model/rule.clj`:
- Line 433: The symbol tool/def-locals is undefined and will cause a runtime
compiler error; remove this debug artifact or replace it with a proper require
of the tool namespace if that macro is actually needed. Locate the stray call to
tool/def-locals in the rule.clj namespace (the lone (tool/def-locals) line) and
either delete it or add the corresponding :require for the tool namespace and
confirm the intended symbol name; prefer removing the line if it was a debug
leftover.

In `@server/src/instant/util/exception.clj`:
- Around line 415-420: The :retry-after computation in
throw-permission-rate-limited! can be negative when retry-at is slightly past;
change the calculation to clamp to a non-negative integer by computing the
seconds as (int (max 0 (/ (.toMillis (Duration/between (Instant/now) retry-at))
1000))) or equivalent so :retry-after is always >= 0 while leaving ::hint
:retry-at unchanged; update the expression that builds ::hint in
throw-permission-rate-limited! to use this clamped value.

---

Nitpick comments:
In `@client/sandbox/react-nextjs/pages/play/rate-limit.tsx`:
- Around line 141-147: The formatError function can return "undefined" when
e.message is absent; update formatError to use a safe fallback for the error
text (e.g., use e?.message, then String(e) or a literal like "Unknown error"
when that is missing) so the returned string is always readable; modify the body
of formatError (the function named formatError and its usage of e.message) to
prefer e?.message ?? String(e) ?? 'Unknown error' (or equivalent) before
interpolating into the returned message.

In `@server/resources/migrations/104_rate_limit_keys.down.sql`:
- Line 1: The down migration uses a hard DROP TABLE which can fail if the table
is already absent; update the statement in 104_rate_limit_keys.down.sql so the
rollback is idempotent by using a conditional drop (e.g., change the DROP TABLE
for rate_limit_keys to use IF EXISTS) so running the down migration repeatedly
or during partial/repair scenarios won't error.

In `@server/src/instant/rate_limit.clj`:
- Around line 83-88: The metric/trace identifier passed to sql/do-execute!
inside the storeAll method is incorrectly ::store; update that call so the
identifier matches the method by using ::storeAll (i.e., replace ::store with
::storeAll in the sql/do-execute! invocation within the storeAll implementation)
so metrics/traces correctly reflect the storeAll operation.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 23d12345-97f6-4ec6-979b-613e65f81d7c

📥 Commits

Reviewing files that changed from the base of the PR and between 8e12145 and e8dde76.

📒 Files selected for processing (12)
  • client/sandbox/react-nextjs/pages/play/rate-limit.tsx
  • client/www/components/dash/Perms.tsx
  • server/resources/migrations/104_rate_limit_keys.down.sql
  • server/resources/migrations/104_rate_limit_keys.up.sql
  • server/src/instant/core.clj
  • server/src/instant/db/cel.clj
  • server/src/instant/db/instaql.clj
  • server/src/instant/model/rule.clj
  • server/src/instant/rate_limit.clj
  • server/src/instant/util/exception.clj
  • server/test/instant/model/rule_test.clj
  • server/test/instant/util/hazelcast_test.clj

Comment thread client/www/components/dash/Perms.tsx Outdated
Comment thread server/src/instant/model/rule.clj Outdated
Comment thread server/src/instant/util/exception.clj Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
server/src/instant/util/exception.clj (1)

419-422: ⚠️ Potential issue | 🟡 Minor

Use ceiling division to avoid premature retries.

The current implementation truncates sub-second delays, so a 500ms wait becomes :retry-after 0. This could cause clients to retry immediately and still be rate-limited. Rounding up ensures clients wait at least until the bucket refills.

🛠️ Proposed fix
 :retry-after (max 0
-              (int (/ (.toMillis (Duration/between (Instant/now)
-                                                   retry-at))
-                       1000)))
+              (int (Math/ceil (/ (.toMillis (Duration/between (Instant/now)
+                                                              retry-at))
+                                 1000.0))))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/instant/util/exception.clj` around lines 419 - 422, The
:retry-after calculation currently truncates sub-second waits by using integer
division on (.toMillis (Duration/between (Instant/now) retry-at)), causing 500ms
to become 0; update the calculation in the retry-after expression to perform
ceiling division (round up) instead of truncation so any non-zero millisecond
delay yields at least 1 second. Locate the expression using (:retry-after (max 0
(int (/ (.toMillis (Duration/between (Instant/now) retry-at)) 1000)))) and
replace the division with a ceiling approach (e.g., divide by 1000.0 and
Math/ceil, or add 999 before integer division) so the computed value is rounded
up to the next second and still clamped by max 0.
🧹 Nitpick comments (1)
server/src/instant/model/rule.clj (1)

373-374: Consider removing or clarifying the XXX comment.

The comment states "Here is where we need to validate the rules are available" but the validation is actually being performed on the very next line by passing rules to cel/validation-errors (which feeds into the rate-limit-validator). This leftover development note may cause confusion.

♻️ Proposed fix
             ;; create the program to see if it throws
             _program (cel/->program ast)
-            ;; XXX: Here is where we need to validate the rules are available
             errors (cel/validation-errors rules compiler ast)]
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/instant/model/rule.clj` around lines 373 - 374, Remove or clarify
the leftover "XXX" dev note: delete the comment ";; XXX: Here is where we need
to validate the rules are available" or replace it with a succinct, accurate
comment that describes what the next line does (e.g., "Validate provided rules
with cel/validation-errors before rate-limit-validator"). Ensure the edit is
made adjacent to the errors binding where cel/validation-errors is invoked so
the comment no longer misleads future readers.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@server/src/instant/util/exception.clj`:
- Around line 419-422: The :retry-after calculation currently truncates
sub-second waits by using integer division on (.toMillis (Duration/between
(Instant/now) retry-at)), causing 500ms to become 0; update the calculation in
the retry-after expression to perform ceiling division (round up) instead of
truncation so any non-zero millisecond delay yields at least 1 second. Locate
the expression using (:retry-after (max 0 (int (/ (.toMillis (Duration/between
(Instant/now) retry-at)) 1000)))) and replace the division with a ceiling
approach (e.g., divide by 1000.0 and Math/ceil, or add 999 before integer
division) so the computed value is rounded up to the next second and still
clamped by max 0.

---

Nitpick comments:
In `@server/src/instant/model/rule.clj`:
- Around line 373-374: Remove or clarify the leftover "XXX" dev note: delete the
comment ";; XXX: Here is where we need to validate the rules are available" or
replace it with a succinct, accurate comment that describes what the next line
does (e.g., "Validate provided rules with cel/validation-errors before
rate-limit-validator"). Ensure the edit is made adjacent to the errors binding
where cel/validation-errors is invoked so the comment no longer misleads future
readers.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 88d7ae6c-639f-451b-a5c5-294e623b350b

📥 Commits

Reviewing files that changed from the base of the PR and between e8dde76 and 1528676.

📒 Files selected for processing (3)
  • client/www/components/dash/Perms.tsx
  • server/src/instant/model/rule.clj
  • server/src/instant/util/exception.clj
✅ Files skipped from review due to trivial changes (1)
  • client/www/components/dash/Perms.tsx

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
server/src/instant/util/hazelcast.clj (1)

337-345: ⚠️ Potential issue | 🟡 Minor

Consider adding error handling for unknown Hazelcast type IDs, or verify old serialized data has been cleaned up.

The magic-code serializers (type IDs 2 and 8) are already removed from serializer-configs. While the backward-compatibility contract (lines 17–30) is well documented, there is no defensive error handling if old persisted data in the MapStore or distributed cache contains those type IDs—deserialization will fail with SerializationException. Either restore the old serializers with graceful degradation, or confirm through MapStore/persistence inspection that no old data remains.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/instant/util/hazelcast.clj` around lines 337 - 345,
serializer-configs was stripped of the old "magic-code" serializers (type IDs 2
and 8) which causes deserialization to throw SerializationException if persisted
maps still contain those type IDs; update the code to defensively handle unknown
type IDs by either re-registering lightweight backward-compatible serializers
for IDs 2 and 8 that map old payloads to a safe fallback type, or add a startup
validation that scans MapStore/distributed maps for entries with type IDs 2/8
and fails fast with a clear remediation message; locate the config list named
serializer-configs and the serialization registration code that originally
mapped type IDs 2 and 8 to implement one of these fixes.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/resources/config/staging.edn`:
- Around line 35-43: The EDN map is malformed and contains duplicate keys and
extra braces; fix the map by removing the stray closing brace that prematurely
ends the map, delete the duplicate :instant-config-app-id entry so the key
appears only once, ensure only one trailing closing brace remains to balance the
opening brace, and keep :database-url, :rate-limit-hmac-key and
:database-cluster-id entries intact (verify the single :instant-config-app-id
value is the intended one).

---

Outside diff comments:
In `@server/src/instant/util/hazelcast.clj`:
- Around line 337-345: serializer-configs was stripped of the old "magic-code"
serializers (type IDs 2 and 8) which causes deserialization to throw
SerializationException if persisted maps still contain those type IDs; update
the code to defensively handle unknown type IDs by either re-registering
lightweight backward-compatible serializers for IDs 2 and 8 that map old
payloads to a safe fallback type, or add a startup validation that scans
MapStore/distributed maps for entries with type IDs 2/8 and fails fast with a
clear remediation message; locate the config list named serializer-configs and
the serialization registration code that originally mapped type IDs 2 and 8 to
implement one of these fixes.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fb3cce3f-312e-4afd-a543-13a44445344e

📥 Commits

Reviewing files that changed from the base of the PR and between 1528676 and f2b8ce7.

📒 Files selected for processing (10)
  • server/resources/config/dev.edn
  • server/resources/config/prod.edn
  • server/resources/config/staging.edn
  • server/resources/migrations/104_rate_limit_keys.up.sql
  • server/src/instant/config.clj
  • server/src/instant/config_edn.clj
  • server/src/instant/rate_limit.clj
  • server/src/instant/util/exception.clj
  • server/src/instant/util/hazelcast.clj
  • server/test/instant/util/hazelcast_test.clj
💤 Files with no reviewable changes (1)
  • server/test/instant/util/hazelcast_test.clj
✅ Files skipped from review due to trivial changes (2)
  • server/resources/config/prod.edn
  • server/resources/migrations/104_rate_limit_keys.up.sql
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/src/instant/util/exception.clj
  • server/src/instant/rate_limit.clj

Comment thread server/resources/config/staging.edn Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/src/instant/db/cel.clj`:
- Around line 1450-1458: The member-overload "limit" with overload-id
"_rateLimit_limit_tokens" currently uses swap! with update-in and returns the
atom state (breaking the declared BOOL return and corrupting the :rate-limits
accumulator); update the impl for [_rateLimit_limit_tokens] to perform a numeric
add against the bucket key using swap! with update (not update-in with a map
argument), and ensure the function returns true (a boolean) after updating
*where-rate-limits*; reference the impl fn for member-overload "limit" and the
atom *where-rate-limits* when making this change.
- Around line 553-559: create-rate-limit-obj currently calls reduce-kv on
(get-in ctx [:rules :code "$rateLimits"]) without guarding for a missing key,
causing exceptions when "$rateLimits" is absent; update create-rate-limit-obj to
default that lookup to an empty map before calling reduce-kv (e.g., use a safe
get-in or or-default pattern) so reduce-kv always receives a map, and apply the
same guarding pattern to the similar use in rate-limit-validation-errors
(referencing the function name rate-limit-validation-errors in
server/src/instant/model/rule.clj) to prevent crashes for apps missing the
"$rateLimits" key.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ad8f2582-dce4-4de2-ae50-2e8dda087b14

📥 Commits

Reviewing files that changed from the base of the PR and between f2b8ce7 and 6f30e84.

📒 Files selected for processing (5)
  • server/resources/config/staging.edn
  • server/src/instant/db/cel.clj
  • server/src/instant/db/instaql.clj
  • server/src/instant/rate_limit.clj
  • server/src/instant/util/exception.clj
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/resources/config/staging.edn
  • server/src/instant/db/instaql.clj
  • server/src/instant/rate_limit.clj

Comment thread server/src/instant/db/cel.clj
Comment thread server/src/instant/db/cel.clj Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
server/src/instant/db/cel.clj (1)

1450-1458: ⚠️ Potential issue | 🔴 Critical

Missing true return in tokens overload breaks BOOL contract.

The _rateLimit_limit_tokens where-overload declares cel-return-type SimpleType/BOOL but swap! returns the updated atom state (a map), not a boolean. Compare with the single-token overload (line 1449) which explicitly returns true.

This will cause type mismatches or unexpected behavior when rules use rateLimit.*.limit(subject, tokens) in where-clauses.

Proposed fix
                            (member-overload "limit"
                                             [{:overload-id "_rateLimit_limit_tokens"
                                               :cel-args [rate-limit-bucket-cel-type SimpleType/DYN SimpleType/INT]
                                               :cel-return-type SimpleType/BOOL
                                               :java-args [RateLimitBucket Object Long]
                                               :impl (fn [[^RateLimitBucket b ^Object k ^Long tokens]]
                                                       (swap! *where-rate-limits*
                                                              update {:bucket b
-                                                                     :bucket-key k} (fnil + 0) tokens))}])])
+                                                                     :bucket-key k} (fnil + 0) tokens)
+                                                       true)}])])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/instant/db/cel.clj` around lines 1450 - 1458, The tokens overload
_rateLimit_limit_tokens currently returns the result of swap! (the updated
*where-rate-limits* map) which violates its declared cel-return-type
SimpleType/BOOL; update the impl for the overload with overload-id
"_rateLimit_limit_tokens" (the fn implementing RateLimitBucket/Object/Long) so
it performs the swap! exactly as it does now but then returns true (boolean)
instead of the swap! result—i.e., call swap! to update *where-rate-limits* and
ensure the final expression / return value of the impl function is true to
satisfy the BOOL contract.
🧹 Nitpick comments (2)
server/test/instant/db/instaql_test.clj (1)

5147-5157: Assert the full page size on the successful reads.

seq will still pass if these queries return only one row, so a partial-result/token-accounting regression could slip through. Since both calls request :limit 2, assert count explicitly here.

Suggested tightening
             (testing "first query succeeds"
-              (is (seq (:users (pretty-perm-q
-                                {:app-id app-id
-                                 :current-user {:id user-id}}
-                                {:users {:$ {:limit 2}}})))))
+              (is (= 2
+                     (count (:users (pretty-perm-q
+                                     {:app-id app-id
+                                      :current-user {:id user-id}}
+                                     {:users {:$ {:limit 2}}}))))))

             (testing "second query succeeds"
-              (is (seq (:users (pretty-perm-q
-                                {:app-id app-id
-                                 :current-user {:id user-id}}
-                                {:users {:$ {:limit 2}}})))))
+              (is (= 2
+                     (count (:users (pretty-perm-q
+                                     {:app-id app-id
+                                      :current-user {:id user-id}}
+                                     {:users {:$ {:limit 2}}}))))))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/test/instant/db/instaql_test.clj` around lines 5147 - 5157, The tests
"first query succeeds" and "second query succeeds" currently assert non-empty
results with seq, which can hide partial results; update both assertions to
check the returned :users vector length equals 2 by using count on the result of
pretty-perm-q for the given {:app-id app-id :current-user {:id user-id}} and
{:users {:$ {:limit 2}}} inputs (look for the pretty-perm-q calls in those two
testing blocks) so the tests explicitly assert full page size rather than just
non-empty.
server/test/instant/rate_limit_test.clj (1)

54-105: Tighten invalid-config assertions to expected error shape.

Lines 55-105 currently assert any Exception. Consider asserting the specific Instant validation error type/message so these tests fail only for real contract regressions, not unrelated runtime exceptions.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/test/instant/rate_limit_test.clj` around lines 54 - 105, Replace the
broad (thrown? Exception ...) assertions in tests referencing
rules-rate-limit-config->limit-config and rules-rate-limit-config->bucket-config
with targeted thrown-with-msg? (or thrown?) checks that assert the specific
exception class and validation message (e.g. IllegalArgumentException or the
concrete Instant validation exception) and a regex matching the expected error
text (like "invalid.*capacity", "period.*too (short|long)", or "Instant").
Update each failing-case line to use thrown-with-msg? with a descriptive regex
so tests only pass when the expected Instant/validation error is raised by
rules-rate-limit-config->limit-config and
rules-rate-limit-config->bucket-config.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@server/test/instant/db/transaction_test.clj`:
- Around line 5893-5895: The test currently builds instance and cluster names
using a 6-digit random integer bound to id, which can collide; replace the
numeric id with a UUID-based string to ensure collision-resistance. In the let
that defines id and the map keys :instance-name and :cluster-name, generate a
UUID (e.g. via java.util.UUID/randomUUID) and append it to "test-instance-" and
"test-cluster-" so each test name is unique and not prone to reuse of an
existing Hazelcast instance.

---

Duplicate comments:
In `@server/src/instant/db/cel.clj`:
- Around line 1450-1458: The tokens overload _rateLimit_limit_tokens currently
returns the result of swap! (the updated *where-rate-limits* map) which violates
its declared cel-return-type SimpleType/BOOL; update the impl for the overload
with overload-id "_rateLimit_limit_tokens" (the fn implementing
RateLimitBucket/Object/Long) so it performs the swap! exactly as it does now but
then returns true (boolean) instead of the swap! result—i.e., call swap! to
update *where-rate-limits* and ensure the final expression / return value of the
impl function is true to satisfy the BOOL contract.

---

Nitpick comments:
In `@server/test/instant/db/instaql_test.clj`:
- Around line 5147-5157: The tests "first query succeeds" and "second query
succeeds" currently assert non-empty results with seq, which can hide partial
results; update both assertions to check the returned :users vector length
equals 2 by using count on the result of pretty-perm-q for the given {:app-id
app-id :current-user {:id user-id}} and {:users {:$ {:limit 2}}} inputs (look
for the pretty-perm-q calls in those two testing blocks) so the tests explicitly
assert full page size rather than just non-empty.

In `@server/test/instant/rate_limit_test.clj`:
- Around line 54-105: Replace the broad (thrown? Exception ...) assertions in
tests referencing rules-rate-limit-config->limit-config and
rules-rate-limit-config->bucket-config with targeted thrown-with-msg? (or
thrown?) checks that assert the specific exception class and validation message
(e.g. IllegalArgumentException or the concrete Instant validation exception) and
a regex matching the expected error text (like "invalid.*capacity", "period.*too
(short|long)", or "Instant"). Update each failing-case line to use
thrown-with-msg? with a descriptive regex so tests only pass when the expected
Instant/validation error is raised by rules-rate-limit-config->limit-config and
rules-rate-limit-config->bucket-config.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 22235ae1-3a6c-4f11-a753-7e6bca9c4dff

📥 Commits

Reviewing files that changed from the base of the PR and between f2b8ce7 and 27ffe6f.

📒 Files selected for processing (11)
  • client/packages/core/src/rulesTypes.ts
  • client/www/lib/permsJsonSchema.tsx
  • server/resources/config/staging.edn
  • server/src/instant/db/cel.clj
  • server/src/instant/db/instaql.clj
  • server/src/instant/rate_limit.clj
  • server/src/instant/util/exception.clj
  • server/test/instant/db/instaql_test.clj
  • server/test/instant/db/transaction_test.clj
  • server/test/instant/model/rule_test.clj
  • server/test/instant/rate_limit_test.clj
✅ Files skipped from review due to trivial changes (1)
  • server/src/instant/rate_limit.clj
🚧 Files skipped from review as they are similar to previous changes (3)
  • server/test/instant/model/rule_test.clj
  • server/src/instant/db/instaql.clj
  • server/src/instant/util/exception.clj

Comment thread server/test/instant/db/transaction_test.clj
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
client/www/lib/permsJsonSchema.tsx (1)

141-163: ⚠️ Potential issue | 🔴 Critical

$rateLimits is still matched as a generic namespace.

JSON Schema applies both properties.$rateLimits and patternProperties to the same key. Since '$rateLimits' matches '^[$a-zA-Z0-9_\\-]+$', valid rate-limit configs are also validated as namespace rule blocks and get rejected because keys like createThings are treated as forbidden properties.

Suggested fix
   return {
     type: 'object',
     properties,
     patternProperties: {
-      '^[$a-zA-Z0-9_\\-]+$': genericNamespaceRuleBlock,
+      '^(?!\\$rateLimits$)[$a-zA-Z0-9_\\-]+$': genericNamespaceRuleBlock,
     },
     additionalProperties: false,
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/www/lib/permsJsonSchema.tsx` around lines 141 - 163, The
patternProperties currently matches keys like '$rateLimits' causing JSON Schema
to apply both properties.$rateLimits and patternProperties to the same key;
update the pattern in patternProperties (where genericNamespaceRuleBlock is
used) to exclude the literal '$rateLimits' (e.g. change '^[$a-zA-Z0-9_\\-]+$' to
a negative-lookahead variant like '^(?!\\$rateLimits$)[$a-zA-Z0-9_\\-]+$') so
that the explicit properties.$rateLimits schema is the only one applied to that
key.
♻️ Duplicate comments (1)
server/src/instant/db/cel.clj (1)

1451-1458: ⚠️ Potential issue | 🔴 Critical

Return a boolean from the token-consuming where overload.

This overload still returns the swap! result, while the declared CEL return type is BOOL and the one-token overload returns true. That lets rateLimit.<bucket>.limit(key, tokens) feed a map into the where-evaluation path instead of a boolean.

🐛 Proposed fix
                            (member-overload "limit"
                                             [{:overload-id "_rateLimit_limit_tokens"
                                               :cel-args [rate-limit-bucket-cel-type SimpleType/DYN SimpleType/INT]
                                               :cel-return-type SimpleType/BOOL
                                               :java-args [RateLimitBucket Object Long]
                                               :impl (fn [[^RateLimitBucket b ^Object k ^Long tokens]]
                                                       (swap! *where-rate-limits*
                                                              update {:bucket b
                                                                      :bucket-key k} (fnil + 0) tokens)
+                                                      true)}])])
-                                                             update {:bucket b
-                                                                     :bucket-key k} (fnil + 0) tokens))}])])
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/src/instant/db/cel.clj` around lines 1451 - 1458, The overload
"_rateLimit_limit_tokens" currently returns the result of swap! (a numeric new
count) but must return a boolean per the declared :cel-return-type BOOL; update
the :impl for that overload (the anonymous fn taking [^RateLimitBucket b ^Object
k ^Long tokens]) to perform the swap! side-effect against *where-rate-limits*
and then return true (e.g., call swap! first and then return true) so
rateLimit.<bucket>.limit(key, tokens) yields a boolean rather than a map/number.
🧹 Nitpick comments (2)
client/packages/core/src/rulesTypes.ts (1)

35-47: fields typing is broader than the validator.

InstantRulesEntityBlock now accepts every non-id attribute in fields, but the permissions schema only allows blob attrs there. That means schema-typed callers can write fields rules that compile and then fail validation later.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@client/packages/core/src/rulesTypes.ts` around lines 35 - 47, The fields
typing in InstantRulesEntityBlock is too broad and should be narrowed to only
blob attributes to match runtime validation; update the fields mapped type in
InstantRulesEntityBlock so the key set filters
Schema['entities'][EntityName]['attrs'] to only those AttrName whose attr type
extends your blob attribute type (e.g. using a conditional key remapping like
[AttrName in Exclude<...> as Schema['entities'][EntityName]['attrs'][AttrName]
extends BlobAttr ? AttrName : never]?: string), keeping the rest of the
InstantRulesEntityBlock (bind, allow) unchanged.
server/test/instant/db/transaction_test.clj (1)

5953-5962: Assert the retry hint, not just the exception kind.

The public contract here is more than “some ::ex/rate-limited exception was thrown” — it also includes the retry metadata. This test would still pass if retry-at / retry-after regressed.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@server/test/instant/db/transaction_test.clj` around lines 5953 - 5962, The
test currently only checks that permissioned-tx/transact! throws a rate-limited
exception via rate-limit-err?; update the catch to also assert the retry hint
metadata (e.g. that (ex-data e) contains a non-nil :retry-at or :retry-after and
that its value is a valid timestamp/number or parsable duration). Specifically,
in the testing block around permissioned-tx/transact! (using make-ctx) replace
the catch-return with assertions that (rate-limit-err? e) is true and that (get
(ex-data e) :retry-after) or :retry-at exists and is of the expected type
(non-nil and numeric/string parseable), failing the test if the retry hint is
missing or invalid.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@client/packages/core/src/rulesTypes.ts`:
- Around line 73-81: The top-level index signature currently allows any string
key to be an InstantRulesRateLimits which lets arbitrary namespaces accept
$rateLimits-shaped maps; change the shape so only an explicit $rateLimits
property (or another dedicated key) can be InstantRulesRateLimits and all other
arbitrary string keys must follow the entity block shape that requires an allow
field. Concretely, update the union on the index signature in rulesTypes.ts:
remove InstantRulesRateLimits from the generic [key: string] union and add a
distinct optional property named "$rateLimits": InstantRulesRateLimits; keep the
entity block type (with bind?, allow, fields?) as the index-signature target so
entities must include allow (refer to InstantRulesRateLimits and
InstantRulesAllowBlock to wire the types correctly).

In `@server/src/instant/db/instaql.clj`:
- Around line 1867-1875: The replay loop in the doseq in instaql.clj is
destructuring rate-limit entries as {:keys [^RateLimitBucket bucket key]} but
the entries are recorded as {:bucket b :bucket-key k} (see cel.clj), so key is
nil and everyone shares the same bucket; change the destructuring to pull
:bucket-key (e.g., {:keys [^RateLimitBucket bucket bucket-key]}) and pass that
bucket-key into (.limit bucket bucket-key ...) (or bind :bucket-key to the name
you expect) so the per-user/per-IP bucket is honored.

In `@server/src/instant/rate_limit.clj`:
- Around line 247-253: The check using (.toHours refill-period) floors the
duration and lets values like "1 day 1 second" slip through; replace the
hours-based comparison with a direct Duration comparison against a 1-day
Duration (e.g. use (.compareTo refill-period (java.time.Duration/ofDays 1)) and
treat > 0 as invalid) in the block that calls ex/throw-validation-err! for the
max limit, and add a regression test asserting that a duration of "1 day 1
second" (or Duration/ofDays 1 plus Duration/ofSeconds 1) is rejected.

In `@server/test/instant/db/instaql_test.clj`:
- Around line 5159-5167: The test currently only verifies the exception type via
rate-limit-err?; update the "third query is rate limited" assertion to capture
the thrown exception (instead of returning false) and assert the rate-limit
payload fields exist and are sane: use ex-data on the caught exception from
pretty-perm-q and assert that :retry-at and :retry-after keys are present (and
numeric or parsable) in addition to keeping the rate-limit type check
(rate-limit-err?). Locate the test block using pretty-perm-q and rate-limit-err?
and add assertions against (ex-data e) for :retry-at and :retry-after.

---

Outside diff comments:
In `@client/www/lib/permsJsonSchema.tsx`:
- Around line 141-163: The patternProperties currently matches keys like
'$rateLimits' causing JSON Schema to apply both properties.$rateLimits and
patternProperties to the same key; update the pattern in patternProperties
(where genericNamespaceRuleBlock is used) to exclude the literal '$rateLimits'
(e.g. change '^[$a-zA-Z0-9_\\-]+$' to a negative-lookahead variant like
'^(?!\\$rateLimits$)[$a-zA-Z0-9_\\-]+$') so that the explicit
properties.$rateLimits schema is the only one applied to that key.

---

Duplicate comments:
In `@server/src/instant/db/cel.clj`:
- Around line 1451-1458: The overload "_rateLimit_limit_tokens" currently
returns the result of swap! (a numeric new count) but must return a boolean per
the declared :cel-return-type BOOL; update the :impl for that overload (the
anonymous fn taking [^RateLimitBucket b ^Object k ^Long tokens]) to perform the
swap! side-effect against *where-rate-limits* and then return true (e.g., call
swap! first and then return true) so rateLimit.<bucket>.limit(key, tokens)
yields a boolean rather than a map/number.

---

Nitpick comments:
In `@client/packages/core/src/rulesTypes.ts`:
- Around line 35-47: The fields typing in InstantRulesEntityBlock is too broad
and should be narrowed to only blob attributes to match runtime validation;
update the fields mapped type in InstantRulesEntityBlock so the key set filters
Schema['entities'][EntityName]['attrs'] to only those AttrName whose attr type
extends your blob attribute type (e.g. using a conditional key remapping like
[AttrName in Exclude<...> as Schema['entities'][EntityName]['attrs'][AttrName]
extends BlobAttr ? AttrName : never]?: string), keeping the rest of the
InstantRulesEntityBlock (bind, allow) unchanged.

In `@server/test/instant/db/transaction_test.clj`:
- Around line 5953-5962: The test currently only checks that
permissioned-tx/transact! throws a rate-limited exception via rate-limit-err?;
update the catch to also assert the retry hint metadata (e.g. that (ex-data e)
contains a non-nil :retry-at or :retry-after and that its value is a valid
timestamp/number or parsable duration). Specifically, in the testing block
around permissioned-tx/transact! (using make-ctx) replace the catch-return with
assertions that (rate-limit-err? e) is true and that (get (ex-data e)
:retry-after) or :retry-at exists and is of the expected type (non-nil and
numeric/string parseable), failing the test if the retry hint is missing or
invalid.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 14f5839a-5fa5-4eb6-b787-c283933f3a21

📥 Commits

Reviewing files that changed from the base of the PR and between f2b8ce7 and 5f30c10.

📒 Files selected for processing (14)
  • client/packages/core/src/rulesTypes.ts
  • client/packages/core/src/utils/fetch.ts
  • client/www/app/docs/rate-limits/page.md
  • client/www/data/docsNavigation.js
  • client/www/lib/permsJsonSchema.tsx
  • server/resources/config/staging.edn
  • server/src/instant/db/cel.clj
  • server/src/instant/db/instaql.clj
  • server/src/instant/rate_limit.clj
  • server/src/instant/util/exception.clj
  • server/test/instant/db/instaql_test.clj
  • server/test/instant/db/transaction_test.clj
  • server/test/instant/model/rule_test.clj
  • server/test/instant/rate_limit_test.clj
✅ Files skipped from review due to trivial changes (2)
  • client/www/data/docsNavigation.js
  • client/packages/core/src/utils/fetch.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • server/src/instant/util/exception.clj
  • server/resources/config/staging.edn

Comment thread client/packages/core/src/rulesTypes.ts
Comment thread server/src/instant/db/instaql.clj Outdated
Comment thread server/src/instant/rate_limit.clj
Comment thread server/test/instant/db/instaql_test.clj Outdated
(def sse-message-type-id 10)
(def wal-record-type-id 11)
(def create-magic-code-key-type-id 12)
(def consume-magic-code-key-type-id 13)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Usually we'd have to wait for the backend to fully deploy before we removed a type, but I've updated the map name. New instances will never see this type, so it's safe to remove.

@dwwoelfel dwwoelfel changed the title WIP on rate limits in rules Add support for rate limits in rules Apr 16, 2026
@dwwoelfel dwwoelfel marked this pull request as ready for review April 16, 2026 16:19
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.

1 participant