Skip to content

fix(rules): recover from compile errors as the official parser does - #248

Merged
sanjevirau merged 4 commits into
mainfrom
fix/229-rules-recovery
Nov 14, 2026
Merged

sanjevirau merged 4 commits into
mainfrom
fix/229-rules-recovery

Conversation

@sanjevirau

@sanjevirau sanjevirau commented Nov 14, 2026 •

Copy link
Copy Markdown
Collaborator

Fixes #229: 25 of its 26 recorded compile-error answers now match the official emulator exactly. The last, version/one, needs version 1 of the rules language and is listed under #247.

Errors after the first

The official parser is ANTLR's, and its default error strategy recovers and goes on reporting; Firenook stopped at the first problem. The parser now recovers as the recordings show:

  • each rule knows what may follow it, and after an error skips to one of those tokens; recovering twice at one token in one state skips one more, so every recovery consumes input or stops;
  • after an error nothing more is reported until a token matches, except the grammar's own messages, and an unwanted token is reported without a recovery (two lines at one position, as the official answers have);
  • at a service, function, match and path segment it looks ahead as the official parser does, so a broken match is read again as a path missing its keyword;
  • a missing token is reported with what fitted where a rule could have ended;
  • errors are listed by position, then message;
  • the lexer reads match paths segment by segment, // is a comment even in a path, and the word after service is its name. An interpolation's text ($(…)) is lexed as the expression it is, so /a/$(/b/c) stays one path (the first CI run caught this, in rules-document-paths-v1 case interpolating-a-literal).

The nesting bounds (240 expression levels, 128 match levels) and their tests are unchanged.

Warnings

crates/rules-engine/src/issues.rs gives the official compiler's warnings the recordings show: an unknown service or method, request and resource where no allow defines them, and parameters and lets nothing reads. A reload that compiles answers them as issues, and a refusal lists them beside its errors.

As officially, rules of an unknown service now compile and govern nothing (every request is denied), and an unknown method grants nothing; they used to be refused. So that a misspelling still shows, each warning is logged as the official CLI logs it after a reload (firestore.rules:2:9 - WARNING Invalid service name: cloud.firestor.), for Firestore and Storage rules, on a reload and also at start, where the official CLI says nothing. Checked against firebase-tools 15.35.0 with a misspelled service: it starts, denies a client write (403) and prints those lines on a reload; this branch does the same, and prints them at start.

Checked

  • The compile-error replay: 119 of 120 recordings match; version/one is listed under Rules: rules_version 1 is evaluated as version 2 #247 and the ledger gate passes both ways.
  • Beyond the recordings, 2,220 sources run through both emulators (single-token changes to three rulesets, plus the 120): on the 2,104 the official emulator refuses, the answers are now identical for 1,855 (800 before), and no source that matched before differs now.
  • Real rulesets compile as before, with no warnings or one.
  • The client replays (rules paths and document names, Storage native clients, kindless queries, listen resume, contributed rooms, write races) pass.
  • The Rust gate (fmt, Clippy, every workspace test) passes on Linux on top of current main.
  • Compiling the frozen 50 KB ruleset: about 0.86 ms before, 0.98 ms after (release build, one machine).

🤖 Generated with Claude Code

https://claude.ai/code/session_01APfZAFCQzCmvhJqWKMT47g

sanjevirau and others added 3 commits November 14, 2026 17:01
A rules reload that does not compile was answered with the official
parser's first problems only: where that parser recovers by skipping
ahead and reports more, Firenook stopped. Its parser now recovers as the
official ANTLR parser's error strategy does, as
developer-tools-compile-errors-v1 records it:

- after an error, the rule it is in skips to a token that may follow a
  rule being parsed, one more when it recovers twice at the same token in
  the same state, and nothing but the grammar's own messages is reported
  until a token is matched;
- at a service, a function, a match and a path's segment it looks ahead
  as the official parser does, reports the first token no form fits, and
  recovers from the rule's start, so a match whose block cannot start is
  read again as a path missing its `match`;
- an unwanted token after a declaration or a let is reported and skipped
  without starting a recovery;
- a missing token is reported with what fitted at the first place since
  where a rule could end (after a path: `{'{', '/', PATH_SEGMENT}`);
- the answer lists the problems by position, then by their text.

The lexer reads a match path segment by segment as the official one does,
`//` as a comment inside a path, and the word after `service` as the
service's name. The official grammar's forms the recovery showed are
taken too: a second service block, a parameter list closed twice, a
wildcard closed twice.

A reload that compiles is answered with the official compiler's warnings
the recordings show (`issues`), and a refusal with them in it: a service
or a method it does not know, whose rules then govern or grant nothing as
the official emulator answers, `request` and `resource` where no allow
naming a method defines them, and parameters and lets nothing reads.

25 of the 26 cases listed under #229 now replay as recorded. version/one
stays listed: the official emulator serves version 1 of the language
there, which no recording shows.

Refs #229

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APfZAFCQzCmvhJqWKMT47g
Rules that compile with warnings are now put in force, as the official
emulator puts them: a misspelled service compiles and governs nothing, and
used to be refused at start. Nothing said so. Each warning is now logged as
the official CLI logs it after a reload (`firestore.rules:2:9 - WARNING
Invalid service name: cloud.firestor.`), for Firestore and Storage rules,
on a reload and also at start, where the official CLI says nothing.

Refs #229

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APfZAFCQzCmvhJqWKMT47g
The one compile-error case #229 leaves, `version/one`, needs version 1 of
the rules language, recorded first: #247.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APfZAFCQzCmvhJqWKMT47g
@sanjevirau sanjevirau added this to the 0.2.5 milestone Nov 14, 2026
The lexer reads a path where a declaration may start (the start of the
source, after `{`, `}` or `;`) as a match path, segment by segment. An
interpolation's text is lexed on its own, so in `/a/$(/b/c)` its start
counted as a declaration's and `/b/c` came out as `/b` and `/c`: the source
was refused (`expected end of path interpolation`) where the official
emulator compiles it (rules-document-paths-v1 interpolating-a-literal). An
interpolation is now lexed as the expression it is, where no declaration
starts.

Refs #229

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01APfZAFCQzCmvhJqWKMT47g
@sanjevirau
sanjevirau force-pushed the fix/229-rules-recovery branch from 23427b4 to 773fe6b Compare November 14, 2026 09:35
@sanjevirau
sanjevirau merged commit 6683b3e into main Nov 14, 2026
23 checks passed
@sanjevirau sanjevirau mentioned this pull request Nov 14, 2026
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.

Rules reload: 26 compile errors still answered otherwise than the official emulator

1 participant