Skip to content

feat: Integrate expression validation into the definition issue framework#795

Merged
kingston merged 4 commits into
mainfrom
kingston/eng-1066-incorporate-expression-validation-into-main-issue-framework
Mar 6, 2026
Merged

feat: Integrate expression validation into the definition issue framework#795
kingston merged 4 commits into
mainfrom
kingston/eng-1066-incorporate-expression-validation-into-main-issue-framework

Conversation

@kingston

@kingston kingston commented Mar 6, 2026

Copy link
Copy Markdown
Collaborator

Summary by CodeRabbit

  • New Features
    • Authorizer expression validation warnings are now surfaced as definition issues (invalid field refs, unknown roles, syntax errors).
    • Editor linter updated to use the new validation flow so expression warnings appear in-place while editing definitions.
  • Changelog
    • Added a release note entry documenting the expression-validation integration.

@changeset-bot

changeset-bot Bot commented Mar 6, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 8cd9635

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 21 packages
Name Type
@baseplate-dev/project-builder-lib Patch
@baseplate-dev/create-project Patch
@baseplate-dev/project-builder-cli Patch
@baseplate-dev/project-builder-common Patch
@baseplate-dev/project-builder-dev Patch
@baseplate-dev/project-builder-server Patch
@baseplate-dev/project-builder-web Patch
@baseplate-dev/plugin-auth Patch
@baseplate-dev/plugin-email Patch
@baseplate-dev/plugin-queue Patch
@baseplate-dev/plugin-rate-limit Patch
@baseplate-dev/plugin-storage Patch
@baseplate-dev/project-builder-test Patch
@baseplate-dev/code-morph Patch
@baseplate-dev/core-generators Patch
@baseplate-dev/fastify-generators Patch
@baseplate-dev/react-generators Patch
@baseplate-dev/sync Patch
@baseplate-dev/tools Patch
@baseplate-dev/ui-components Patch
@baseplate-dev/utils Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@coderabbitai

coderabbitai Bot commented Mar 6, 2026

Copy link
Copy Markdown

Warning

Rate limit exceeded

@kingston has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 9 minutes and 23 seconds before requesting another review.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: fe0c131d-47c3-453f-8901-b07041454e28

📥 Commits

Reviewing files that changed from the base of the PR and between 82864c0 and 8cd9635.

📒 Files selected for processing (8)
  • .github/workflows/verify-examples-sync.yml
  • AGENTS.md
  • packages/project-builder-lib/src/parser/collect-expression-issues.ts
  • packages/project-builder-lib/src/parser/collect-expression-issues.unit.test.ts
  • packages/project-builder-lib/src/references/expression-types.ts
  • packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-parser.ts
  • packages/project-builder-lib/src/testing/expression-stub-parser.test-helper.ts
  • packages/project-builder-lib/src/testing/expression-warning-parser.test-helper.ts
📝 Walkthrough

Walkthrough

This PR integrates expression validation into the definition-issue pipeline by adding a collector for expression parser warnings, introducing an ExpressionValidationContext, and refactoring authorizer expression validators and related web UI code to consume pluginStore + definition. Expression parser warnings are surfaced as definition issues with warning severity.

Changes

Cohort / File(s) Summary
Expression Validation Foundation
packages/project-builder-lib/src/parser/collect-expression-issues.ts, packages/project-builder-lib/src/parser/collect-expression-issues.unit.test.ts, packages/project-builder-lib/src/references/expression-types.ts
New collectExpressionIssues collects expression metadata, runs registered expression parsers, and maps parser warnings to DefinitionIssue objects. Adds ExpressionValidationContext (contains definition and pluginStore) and unit tests covering no-expr, no-warnings, multi-warning, and parser error cases.
Definition Issues Integration
packages/project-builder-lib/src/parser/collect-definition-issues.ts, packages/project-builder-lib/src/parser/collect-definition-issues.unit.test.ts, packages/project-builder-lib/src/parser/index.ts
Integrated collectExpressionIssues into collectDefinitionIssues pipeline and re-exported it. Tests updated to assert expression warnings are appended to definition issues with correct message, path, and severity.
Authorizer Expression Validators
packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-parser.ts, packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.ts, packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.unit.test.ts
Refactored validators to accept ExpressionValidationContext (and pluginStore + raw definition) instead of ProjectDefinitionContainer. Role resolution now uses pluginStore.use(...) and model context is derived from context.definition. Tests updated to use mock PluginSpecStore and pass definition separately.
Test Infrastructure
packages/project-builder-lib/src/testing/expression-warning-parser.test-helper.ts
Added WarningParser and ThrowingParser test helpers implementing RefExpressionParser for deterministic warning and failure scenarios used by unit tests.
Web UI Updates
packages/project-builder-web/src/routes/data/models/edit.$key/-components/authorizer/authorizer-expression-linter.ts, packages/project-builder-web/src/routes/data/models/edit.$key/-components/authorizer/model-authorizer-role-form.tsx
Updated linter factory to accept pluginStore and definition instead of ProjectDefinitionContainer. Call sites updated to pass the new parameters.
Release Documentation
.changeset/expression-validation-issues.md
Adds changelog entry noting expression validation integration and that authorizer expression warnings are surfaced as definition issues (warning severity).
CI minor change
.github/workflows/verify-examples-sync.yml
Removed --compact flag from example sync diff command.

Sequence Diagram(s)

sequenceDiagram
    participant Collector as collectDefinitionIssues
    participant ExprCollector as collectExpressionIssues
    participant Schema as Schema (zod)
    participant Parser as RefExpressionParser
    participant Store as PluginSpecStore
    participant Issues as DefinitionIssues

    Collector->>ExprCollector: extract expressions from Schema + data
    ExprCollector->>Parser: parse(expression.value)
    Parser-->>ExprCollector: parseResult / throws
    ExprCollector->>Parser: getWarnings(parseResult, ExpressionValidationContext{definition, pluginStore})
    Parser-->>ExprCollector: [warnings]
    ExprCollector->>Issues: map warnings -> DefinitionIssue(path, message, severity: warning)
    ExprCollector-->>Collector: [DefinitionIssue...]
    Collector->>Issues: append expression issues to final issues array
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • PR #779: Main PR extends this foundational definition-issue pipeline by adding expression validation integration via collectExpressionIssues and invoking it from collectDefinitionIssues.
  • PR #737: Main PR's expression-validation work depends on and updates PluginSpecStore types and APIs introduced in this PR, directly overlapping with its plugin-spec refactoring.
  • PR #753: Both PRs modify the authorizer expression parsing/validation pipeline; the prior PR adds authorizer infrastructure, and this PR updates those modules to use ExpressionValidationContext and surface parser warnings as definition issues.
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately and concisely summarizes the main objective: integrating expression validation into the definition issue framework, which is reflected across all file changes.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch kingston/eng-1066-incorporate-expression-validation-into-main-issue-framework

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (2)
packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.unit.test.ts (1)

23-29: Make the mock depend on the passed definition.

The real authConfigSpec.use() contract is getAuthConfig(definition), but this stub ignores definition entirely. As written, these tests stay green even if the new pluginStore + definition plumbing is broken, so the main refactor is not actually exercised.

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

In
`@packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.unit.test.ts`
around lines 23 - 29, createMockPluginStore returns a PluginSpecStore whose
stubbed authConfigSpec.use() ignores the incoming definition; update
createMockPluginStore so authConfigSpec.use() returns an object whose
getAuthConfig takes the passed definition and uses it (e.g., selects roles based
on definition or returns a structure derived from it) so tests exercise the real
contract getAuthConfig(definition). Locate createMockPluginStore and the stub
for authConfigSpec.use()/getAuthConfig and change getAuthConfig to accept the
definition parameter and respond deterministically based on that parameter
instead of closing over the outer roles only.
packages/project-builder-lib/src/parser/collect-definition-issues.unit.test.ts (1)

257-287: Add one end-to-end case with the real authorizer parser.

This proves the new wiring with a stubbed warning parser, but it still doesn't pin the user-facing path this PR is advertising. A single case using the real authorizer parser would catch drift if malformed expressions stop surfacing through collectDefinitionIssues() as warnings.

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

In
`@packages/project-builder-lib/src/parser/collect-definition-issues.unit.test.ts`
around lines 257 - 287, Add an end-to-end test that uses the real authorizer
expression parser instead of the stubbed WarningParser to ensure expression
warnings surface through collectDefinitionIssues; update the test block (same
describe 'collectDefinitionIssues') to create a schema via definitionSchema
where the condition uses ctx.withExpression(realAuthorizerParser) (or the actual
exported authorizer parser used in production), instantiate the parser context
with createDefinitionSchemaParserContext({ plugins: pluginStore }), pass data
that triggers a known authorizer warning, call collectDefinitionIssues(schema,
data, pluginStore), and assert the returned issues include the expected warning
with path ['condition'] and severity 'warning'.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/project-builder-lib/src/parser/collect-definition-issues.ts`:
- Around line 96-98: collectExpressionIssues currently lets any parser.parse()
or parser.getWarnings() exception bubble up and abort collectDefinitionIssues;
update the expression collection to be fail-soft so a single throwing parser
produces a warning rather than throwing. Specifically, modify
collectExpressionIssues (or wrap the call in collectDefinitionIssues around
collectExpressionIssues) so that each parser invocation (parser.parse /
parser.getWarnings) is run in a try/catch, and on exception push a normalized
warning issue into the returned array (including parser name/expression context
and the error message) instead of rethrowing; ensure collectDefinitionIssues
still spreads the returned issues (issues.push(...expressionIssues)) unchanged.

In
`@packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.ts`:
- Around line 43-50: getRoleNames currently assumes
authConfig.getAuthConfig(definition as ProjectDefinition) returns an object with
a roles array and crashes when it is undefined; update getRoleNames to
defensively handle missing/undefined return values from authConfig.getAuthConfig
by checking the result before using roles (e.g., ensure you read the authConfig
result into a local variable, validate it or treat it as nullable, then build
the Set from roles?.map(role => role.name) ?? []), and also validate or guard
the unsafe cast of definition before passing it to authConfig.getAuthConfig so
plugin implementations that expect a parsed ProjectDefinition won't cause
runtime failures (refer to getRoleNames, pluginStore.use,
authConfig.getAuthConfig, ProjectDefinition, and the local roles variable).

---

Nitpick comments:
In
`@packages/project-builder-lib/src/parser/collect-definition-issues.unit.test.ts`:
- Around line 257-287: Add an end-to-end test that uses the real authorizer
expression parser instead of the stubbed WarningParser to ensure expression
warnings surface through collectDefinitionIssues; update the test block (same
describe 'collectDefinitionIssues') to create a schema via definitionSchema
where the condition uses ctx.withExpression(realAuthorizerParser) (or the actual
exported authorizer parser used in production), instantiate the parser context
with createDefinitionSchemaParserContext({ plugins: pluginStore }), pass data
that triggers a known authorizer warning, call collectDefinitionIssues(schema,
data, pluginStore), and assert the returned issues include the expected warning
with path ['condition'] and severity 'warning'.

In
`@packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.unit.test.ts`:
- Around line 23-29: createMockPluginStore returns a PluginSpecStore whose
stubbed authConfigSpec.use() ignores the incoming definition; update
createMockPluginStore so authConfigSpec.use() returns an object whose
getAuthConfig takes the passed definition and uses it (e.g., selects roles based
on definition or returns a structure derived from it) so tests exercise the real
contract getAuthConfig(definition). Locate createMockPluginStore and the stub
for authConfigSpec.use()/getAuthConfig and change getAuthConfig to accept the
definition parameter and respond deterministically based on that parameter
instead of closing over the outer roles only.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 67de19ae-182e-4d2c-823a-954c03ef31ba

📥 Commits

Reviewing files that changed from the base of the PR and between e796b7c and f8945c2.

📒 Files selected for processing (13)
  • .changeset/expression-validation-issues.md
  • packages/project-builder-lib/src/parser/collect-definition-issues.ts
  • packages/project-builder-lib/src/parser/collect-definition-issues.unit.test.ts
  • packages/project-builder-lib/src/parser/collect-expression-issues.ts
  • packages/project-builder-lib/src/parser/collect-expression-issues.unit.test.ts
  • packages/project-builder-lib/src/parser/index.ts
  • packages/project-builder-lib/src/references/expression-types.ts
  • packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-parser.ts
  • packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.ts
  • packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.unit.test.ts
  • packages/project-builder-lib/src/testing/expression-warning-parser.test-helper.ts
  • packages/project-builder-web/src/routes/data/models/edit.$key/-components/authorizer/authorizer-expression-linter.ts
  • packages/project-builder-web/src/routes/data/models/edit.$key/-components/authorizer/model-authorizer-role-form.tsx

Comment on lines +96 to +98
// Collect expression validation issues
const expressionIssues = collectExpressionIssues(schema, data, pluginStore);
issues.push(...expressionIssues);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Make expression collection fail-soft before calling it here.

collectExpressionIssues() currently invokes each parser’s parse() and getWarnings() directly. Wiring it into collectDefinitionIssues() means a single throwing expression parser now aborts the entire definition-issue pass instead of surfacing a warning for that expression.

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

In `@packages/project-builder-lib/src/parser/collect-definition-issues.ts` around
lines 96 - 98, collectExpressionIssues currently lets any parser.parse() or
parser.getWarnings() exception bubble up and abort collectDefinitionIssues;
update the expression collection to be fail-soft so a single throwing parser
produces a warning rather than throwing. Specifically, modify
collectExpressionIssues (or wrap the call in collectDefinitionIssues around
collectExpressionIssues) so that each parser invocation (parser.parse /
parser.getWarnings) is run in a try/catch, and on exception push a normalized
warning issue into the returned array (including parser name/expression context
and the error message) instead of rethrowing; ensure collectDefinitionIssues
still spreads the returned issues (issues.push(...expressionIssues)) unchanged.

Comment on lines +43 to +50
function getRoleNames(
pluginStore: PluginSpecStore,
definition: unknown,
): Set<string> {
const authConfig = pluginStore.use(authConfigSpec);

const roles = authConfig.getAuthConfig(
definition as ProjectDefinition,

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "=== collectExpressionIssues.ts (first 150 lines) ==="
head -150 packages/project-builder-lib/src/parser/collect-expression-issues.ts

echo ""
echo "=== authorizer-expression-validator.ts (full file) ==="
cat packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.ts

echo ""
echo "=== All getAuthConfig implementations ==="
rg -n -B2 -A8 '\bgetAuthConfig\b' packages/project-builder-lib/src --type ts

Repository: halfdomelabs/baseplate

Length of output: 11500


🏁 Script executed:

#!/bin/bash
# Check if there are concrete getAuthConfig implementations in plugin files
find packages -name "*.ts" -type f | xargs rg -l "getAuthConfig.*=" | head -20

Repository: halfdomelabs/baseplate

Length of output: 234


🏁 Script executed:

#!/bin/bash
# Look for auth config plugin implementations
rg -n "getAuthConfig\s*[:=]" packages --type ts -B5 -A10 | head -80

Repository: halfdomelabs/baseplate

Length of output: 4223


Fix crash in getRoleNames() when getAuthConfig() returns undefined or missing roles.

The unsafe cast definition as ProjectDefinition is confirmed. More critically, getRoleNames() crashes with a TypeError if authConfig.getAuthConfig() returns undefined/null or an object without a roles property: the expression roles?.map((role) => role.name) returns undefined, and new Set(undefined) throws "undefined is not iterable."

Add a fallback to safely handle missing roles:

return new Set(roles?.map((role) => role.name) ?? []);

Additionally, validate the cast or add a runtime check before passing the unparsed definition to getAuthConfig(), as plugin implementations may assume a fully parsed ProjectDefinition.

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

In
`@packages/project-builder-lib/src/schema/models/authorizer/authorizer-expression-validator.ts`
around lines 43 - 50, getRoleNames currently assumes
authConfig.getAuthConfig(definition as ProjectDefinition) returns an object with
a roles array and crashes when it is undefined; update getRoleNames to
defensively handle missing/undefined return values from authConfig.getAuthConfig
by checking the result before using roles (e.g., ensure you read the authConfig
result into a local variable, validate it or treat it as nullable, then build
the Set from roles?.map(role => role.name) ?? []), and also validate or guard
the unsafe cast of definition before passing it to authConfig.getAuthConfig so
plugin implementations that expect a parsed ProjectDefinition won't cause
runtime failures (refer to getRoleNames, pluginStore.use,
authConfig.getAuthConfig, ProjectDefinition, and the local roles variable).

@kingston kingston merged commit dfa9638 into main Mar 6, 2026
9 checks passed
@kingston kingston deleted the kingston/eng-1066-incorporate-expression-validation-into-main-issue-framework branch March 6, 2026 11:23
@github-actions github-actions Bot mentioned this pull request Mar 6, 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.

1 participant