Skip to content

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Oct 17, 2025

Summary by CodeRabbit

  • New Features

    • Policy system available as a plugin with a policy DSL and helper functions for authorization.
    • Models can declare per-model/record access control via @@Allow / @@deny attributes.
    • Sample app demonstrates policy-based, per-user authorization.
  • Tooling

    • Schema loading and generation tools now recognize and incorporate plugin-provided policy models.
  • Tests

    • End-to-end tests and test tooling updated to exercise policy scenarios.

Copilot AI review requested due to automatic review settings October 17, 2025 04:34
@coderabbitai
Copy link

coderabbitai bot commented Oct 17, 2025

Note

Other AI code review bot(s) detected

CodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review.

Walkthrough

This PR moves policy DSL declarations from the core stdlib into a dedicated policy plugin, adds plugin discovery and loading into schema parsing, updates runtime/SDK exports to support plugin-driven utilities, and wires the policy plugin into samples, tests, and tooling.

Changes

Cohort / File(s) Summary
Configuration & Constants
\.vscode/launch.json, packages/cli/src/constants.ts
Adjusted debug working directory; added PLUGIN_MODULE_NAME = 'plugin.zmodel'.
Plugin discovery & CLI
packages/cli/src/actions/action-utils.ts, packages/cli/src/actions/generate.ts
Added getPluginDocuments() to discover/resolved plugin model files and integrate them into schema loading; make CLI plugin import optional (guarded).
Language loader / docs
packages/language/src/index.ts, packages/language/test/utils.ts
Renamed local plugin variable to additionalDocs/pluginDocs and pass additional model files into document builder/load calls.
Stdlib → Policy plugin DSL
packages/language/res/stdlib.zmodel, packages/plugins/policy/plugin.zmodel
Removed policy helpers (@@allow, @@deny, currentModel, currentOperation, check, before) from stdlib and added equivalent declarations in plugin.zmodel.
Language validators / utils
packages/language/src/utils.ts, packages/language/src/validators/function-invocation-validator.ts
Dropped stdlib-origin guards for before invocations and simplified invocation validation to work without stdlib constraints.
Policy plugin package & code
packages/plugins/policy/package.json, packages/plugins/policy/src/column-collector.ts, packages/plugins/policy/src/policy-handler.ts
Exported plugin.zmodel in package.json; removed @zenstackhq/sdk dependency entry; updated base classes to use KyselyUtils/SchemaUtils from runtime.
Runtime & SDK exports
packages/runtime/src/index.ts, packages/runtime/src/utils/schema-utils.ts, packages/sdk/src/index.ts, packages/sdk/src/model-utils.ts
Re-exported KyselyUtils and SchemaUtils from runtime; adjusted some schema-utils imports; removed certain SDK re-exports and added ModelUtils; converted a Model import to a type-only import.
Test tools & helpers
packages/testtools/src/utils.ts, packages/testtools/src/schema.ts, packages/testtools/src/client.ts, tests/e2e/orm/scripts/generate.ts
Added loadDocumentWithPlugins() and switched callers to the plugin-aware loader so tests/tools load plugin model files.
Samples & schema changes
samples/blog/main.ts, samples/blog/package.json, samples/blog/zenstack/schema.ts, samples/blog/zenstack/schema.zmodel, samples/blog/zenstack/models.ts
Added PolicyPlugin usage and dependency; added access-control attributes (@@allow) to models; minor doc comment removal.
Tests updates
tests/e2e/orm/policy/migrated/current-model.test.ts, tests/e2e/orm/policy/migrated/current-operation.test.ts, packages/language/test/utils.ts, packages/testtools/*
Updated test schemas to add @id on certain fields; adjusted test helpers to load plugin models.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

🐇 From the burrow I hop with a model in paw,
Plugin.zmodel now holds policy law.
Stdlib lightened, discovery in tow,
Schemas bloom where the policy rules go.
Hooray for plugins — I nibble the new flow! 🥕

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The PR title "refactor: move policy attributes to its own zmodel" directly and accurately describes the primary change in this changeset. The core modification is extracting policy-related declarations (@@Allow, @@deny, currentModel, currentOperation, check, before) from packages/language/res/stdlib.zmodel and moving them to a newly created packages/plugins/policy/plugin.zmodel file. The title is concise, specific, and clearly conveys the refactoring objective without ambiguity or vague language.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/plugin-zmodel

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.

@claude
Copy link

claude bot commented Oct 17, 2025

Claude finished @ymc9's task —— View job


PR Review: Move Policy Attributes to Own ZModel

I've completed a comprehensive review of this PR that moves policy attributes from the core stdlib.zmodel to a separate plugin-specific plugin.zmodel file. This is an excellent architectural improvement.

Review Tasks:

  • Understand the refactoring scope and architecture changes
  • Review the new plugin.zmodel structure
  • Analyze CLI integration changes
  • Check runtime and SDK modifications
  • Evaluate test coverage and sample updates
  • Assess code quality, bugs, performance, and security

Positive Aspects

1. Excellent Separation of Concerns

  • Clean Architecture: Moving policy-specific attributes (@@allow, @@deny) and functions (check, before, currentModel, currentOperation) to a dedicated plugin module is a great design decision
  • Modular Design: This change enables the policy functionality to be truly plugin-based rather than hardcoded in the core language

2. Proper Plugin Discovery Mechanism

  • Smart Resolution: The CLI now properly discovers plugin.zmodel files via multiple strategies:
    • Local file path resolution
    • ESM module resolution (import.meta.resolve)
    • Upward directory traversal (findUp)
  • Good File Structure: Uses PLUGIN_MODULE_NAME = 'plugin.zmodel' constant for consistency

3. Comprehensive Integration

  • Package.json Export: The policy plugin properly exports the plugin.zmodel file in packages/plugins/policy/package.json:30-33
  • Test Infrastructure: Updated test utilities (loadDocumentWithPlugins) to include plugin models
  • Sample Updates: Blog sample correctly demonstrates the new plugin usage

🔍 Code Quality Analysis

1. Implementation Quality

  • Error Handling: Good error handling in getPluginDocuments() at packages/cli/src/actions/action-utils.ts:98-103
  • Validation: Proper syntax error checking before processing plugins (action-utils.ts:61-63)
  • Type Safety: Maintains strong typing throughout the refactoring

2. Documentation and Comments

  • Good Comments: Policy functions have clear JSDoc documentation in plugin.zmodel
  • Context Validation: Function validation properly handles policy-specific context (function-invocation-validator.ts:229-305)

⚠️ Areas of Concern

1. Hard-coded Path Dependencies

// packages/testtools/src/utils.ts:5
const pluginModelFiles = [path.resolve(__dirname, '../node_modules/@zenstackhq/plugin-policy/plugin.zmodel')];

// tests/e2e/orm/scripts/generate.ts:26
const pluginDocs = [path.resolve(_dirname, '../../node_modules/@zenstackhq/plugin-policy/plugin.zmodel')];

Issue: These hard-coded paths are brittle and will break if:

  • Plugin is installed in a different location
  • Multiple versions of the plugin exist
  • Plugin is loaded from a different package manager structure

Recommendation: Use the same dynamic discovery mechanism from getPluginDocuments() instead of hard-coded paths.

2. Missing Error Cases

  • No validation for when plugin.zmodel file exists but contains syntax errors
  • Limited feedback when plugin discovery fails silently (action-utils.ts:100-102)

3. Function Validation Location

The check() function validation is still in the core language validator (function-invocation-validator.ts:229-305) with a TODO comment:

// TODO: move this to policy plugin
@func('check')

This creates a dependency from core to policy plugin concepts.


🚀 Performance Considerations

Positive

  • Plugin discovery only happens during schema loading, not runtime
  • File system operations are properly cached/memoized where needed
  • No runtime performance impact

Areas for Improvement

  • Multiple file system operations during plugin discovery could be optimized
  • Consider caching resolved plugin paths

🔒 Security Analysis

Safe Practices

  • ✅ No direct file path injection vulnerabilities
  • ✅ Proper validation of plugin provider values
  • ✅ Safe ESM module resolution with try-catch

Potential Concerns

  • Plugin loading via import() could theoretically load arbitrary code, but this is by design and expected behavior
  • File system traversal is properly constrained

📝 Test Coverage

Comprehensive Updates

  • ✅ E2E tests updated to use new plugin system
  • ✅ Policy-specific tests maintained functionality
  • ✅ Sample applications demonstrate correct usage

Missing Coverage

  • No specific tests for plugin discovery failure scenarios
  • Limited testing of edge cases in getPluginDocuments()

🎯 Recommendations

  1. Fix Hard-coded Paths (High Priority)

    • Replace hard-coded plugin paths in test utilities with dynamic discovery
    • Use the CLI's getPluginDocuments() logic consistently
  2. Move Policy Validation (Medium Priority)

    • Complete the TODO by moving check() function validation to the policy plugin
    • This would remove the circular dependency
  3. Improve Error Handling (Medium Priority)

    • Add better error messages when plugin files are malformed
    • Provide clearer feedback when plugin discovery fails
  4. Add Tests (Low Priority)

    • Test plugin discovery edge cases
    • Test behavior with malformed plugin.zmodel files

Overall Assessment

This is a well-executed refactoring that significantly improves the architecture by:

  • Making policy functionality truly modular
  • Enabling better plugin ecosystem development
  • Maintaining backward compatibility
  • Following good separation of concerns principles

The implementation is largely sound with only a few minor issues that should be addressed. The code quality is high and the changes are well-integrated across the codebase.

Recommendation: ✅ Approve with minor fixes

The hard-coded path dependencies should be addressed, but they don't prevent the PR from being merged as they're isolated to test infrastructure.

@claude
Copy link

claude bot commented Oct 17, 2025

Claude finished @ymc9's task —— View job


PR Review: Move Policy Attributes to Own ZModel

I've completed a comprehensive review of this PR that moves policy attributes from the core stdlib.zmodel to a separate plugin-specific plugin.zmodel file. This is an excellent architectural improvement.

Review Tasks:

  • Understand the refactoring scope and architecture changes
  • Review the new plugin.zmodel structure
  • Analyze CLI integration changes
  • Check runtime and SDK modifications
  • Evaluate test coverage and sample updates
  • Assess code quality, bugs, performance, and security

Positive Aspects

1. Excellent Separation of Concerns

  • Clean Architecture: Moving policy-specific attributes (@@allow, @@deny) and functions (check, before, currentModel, currentOperation) to a dedicated plugin module is a great design decision
  • Modular Design: This change enables the policy functionality to be truly plugin-based rather than hardcoded in the core language

2. Proper Plugin Discovery Mechanism

  • Smart Resolution: The CLI now properly discovers plugin.zmodel files via multiple strategies:
    • Local file path resolution
    • ESM module resolution (import.meta.resolve)
    • Upward directory traversal (findUp)
  • Good File Structure: Uses PLUGIN_MODULE_NAME = 'plugin.zmodel' constant for consistency

3. Comprehensive Integration

  • Package.json Export: The policy plugin properly exports the plugin.zmodel file in packages/plugins/policy/package.json:30-33
  • Test Infrastructure: Updated test utilities (loadDocumentWithPlugins) to include plugin models
  • Sample Updates: Blog sample correctly demonstrates the new plugin usage

🔍 Code Quality Analysis

1. Implementation Quality

  • Error Handling: Good error handling in getPluginDocuments() at packages/cli/src/actions/action-utils.ts:98-103
  • Validation: Proper syntax error checking before processing plugins (action-utils.ts:61-63)
  • Type Safety: Maintains strong typing throughout the refactoring

2. Documentation and Comments

  • Good Comments: Policy functions have clear JSDoc documentation in plugin.zmodel
  • Context Validation: Function validation properly handles policy-specific context (function-invocation-validator.ts:229-305)

⚠️ Areas of Concern

1. Hard-coded Path Dependencies

// packages/testtools/src/utils.ts:5
const pluginModelFiles = [path.resolve(__dirname, '../node_modules/@zenstackhq/plugin-policy/plugin.zmodel')];

// tests/e2e/orm/scripts/generate.ts:26
const pluginDocs = [path.resolve(_dirname, '../../node_modules/@zenstackhq/plugin-policy/plugin.zmodel')];

Issue: These hard-coded paths are brittle and will break if:

  • Plugin is installed in a different location
  • Multiple versions of the plugin exist
  • Plugin is loaded from a different package manager structure

Recommendation: Use the same dynamic discovery mechanism from getPluginDocuments() instead of hard-coded paths.

2. Missing Error Cases

  • No validation for when plugin.zmodel file exists but contains syntax errors
  • Limited feedback when plugin discovery fails silently (action-utils.ts:100-102)

3. Function Validation Location

The check() function validation is still in the core language validator (function-invocation-validator.ts:229-305) with a TODO comment:

// TODO: move this to policy plugin
@func('check')

This creates a dependency from core to policy plugin concepts.


🚀 Performance Considerations

Positive

  • Plugin discovery only happens during schema loading, not runtime
  • File system operations are properly cached/memoized where needed
  • No runtime performance impact

Areas for Improvement

  • Multiple file system operations during plugin discovery could be optimized
  • Consider caching resolved plugin paths

🔒 Security Analysis

Safe Practices

  • ✅ No direct file path injection vulnerabilities
  • ✅ Proper validation of plugin provider values
  • ✅ Safe ESM module resolution with try-catch

Potential Concerns

  • Plugin loading via import() could theoretically load arbitrary code, but this is by design and expected behavior
  • File system traversal is properly constrained

📝 Test Coverage

Comprehensive Updates

  • ✅ E2E tests updated to use new plugin system
  • ✅ Policy-specific tests maintained functionality
  • ✅ Sample applications demonstrate correct usage

Missing Coverage

  • No specific tests for plugin discovery failure scenarios
  • Limited testing of edge cases in getPluginDocuments()

🎯 Recommendations

  1. Fix Hard-coded Paths (High Priority)

    • Replace hard-coded plugin paths in test utilities with dynamic discovery
    • Use the CLI's getPluginDocuments() logic consistently
  2. Move Policy Validation (Medium Priority)

    • Complete the TODO by moving check() function validation to the policy plugin
    • This would remove the circular dependency
  3. Improve Error Handling (Medium Priority)

    • Add better error messages when plugin files are malformed
    • Provide clearer feedback when plugin discovery fails
  4. Add Tests (Low Priority)

    • Test plugin discovery edge cases
    • Test behavior with malformed plugin.zmodel files

Overall Assessment

This is a well-executed refactoring that significantly improves the architecture by:

  • Making policy functionality truly modular
  • Enabling better plugin ecosystem development
  • Maintaining backward compatibility
  • Following good separation of concerns principles

The implementation is largely sound with only a few minor issues that should be addressed. The code quality is high and the changes are well-integrated across the codebase.

Recommendation: ✅ Approve with minor fixes

The hard-coded path dependencies should be addressed, but they don't prevent the PR from being merged as they're isolated to test infrastructure.


Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Refactors policy-related DSL constructs out of the stdlib into a dedicated plugin module and wires up discovery/loading of plugin-contributed model files across CLI, tests, and samples.

  • Introduce plugin.zmodel for the policy plugin and update sample schema to consume it.
  • Update loaders (CLI/test tools/scripts) to include plugin models during parsing and generation.
  • Move/adjust utilities: use runtime’s SchemaUtils/KyselyUtils in policy plugin; tweak SDK exports.

Reviewed Changes

Copilot reviewed 27 out of 29 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
tests/e2e/orm/scripts/generate.ts Script now loads plugin.zmodel when generating TS schema for tests.
tests/e2e/orm/policy/migrated/current-operation.test.ts Test schema updated to mark id as @id.
tests/e2e/orm/policy/migrated/current-model.test.ts Test schema updated to mark id as @id.
samples/blog/zenstack/schema.zmodel Adds policy plugin provider and model-level allow rules.
samples/blog/zenstack/schema.ts Generated schema now contains policy attributes (ExpressionUtils usage).
samples/blog/zenstack/models.ts Removes extra comment lines (doc comment change only).
samples/blog/package.json Adds dependency on @zenstackhq/plugin-policy.
samples/blog/main.ts Demonstrates policy-enabled reads via PolicyPlugin.
packages/testtools/src/utils.ts Adds helper to load language with plugin model files.
packages/testtools/src/schema.ts Uses loadDocumentWithPlugins to generate/load schemas in tests.
packages/testtools/src/client.ts Uses loadDocumentWithPlugins when using prisma push path.
packages/sdk/src/model-utils.ts Minor type-only import adjustment for Model.
packages/sdk/src/index.ts Stops re-exporting expression-utils and default-operation-node-visitor.
packages/runtime/src/utils/schema-utils.ts Corrects schema types import path; centralizes ExpressionVisitor.
packages/runtime/src/index.ts Re-exports KyselyUtils and SchemaUtils from runtime.
packages/plugins/policy/src/policy-handler.ts Uses SchemaUtils.ExpressionVisitor from runtime.
packages/plugins/policy/src/column-collector.ts Uses KyselyUtils.DefaultOperationNodeVisitor from runtime.
packages/plugins/policy/plugin.zmodel New policy plugin DSL surface (attributes/functions).
packages/plugins/policy/package.json Publishes plugin.zmodel via files/exports.
packages/language/src/validators/function-invocation-validator.ts Removes stdlib-only guard; validator logic no longer limited to stdlib functions.
packages/language/src/utils.ts Loosens isBeforeInvocation to not rely on stdlib origin.
packages/language/src/index.ts Renames pluginDocs to additionalDocs and builds with additional docs.
packages/language/res/stdlib.zmodel Removes policy constructs from stdlib.
packages/cli/src/constants.ts Adds PLUGIN_MODULE_NAME constant.
packages/cli/src/actions/generate.ts Tolerates plugins without generators.
packages/cli/src/actions/action-utils.ts Loads plugin model files discovered from plugin provider values.
.vscode/launch.json Updates sample launch cwd.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Copy link

@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)
packages/plugins/policy/plugin.zmodel (1)

17-17: Note: commented-out field-level attributes.

The commented-out @allow (line 17) and @deny (line 33) field-level attributes suggest these features are planned but not yet implemented. This is fine for a refactor PR focused on moving existing functionality.

If field-level policies are not planned for the near term, consider adding a TODO comment explaining the deferral reason, or remove these placeholders to reduce confusion.

Also applies to: 33-33

tests/e2e/orm/scripts/generate.ts (1)

32-32: Consider removing redundant type cast.

The cast as Model appears unnecessary since result.model is already typed as Model according to the loadDocument return type signature.

Apply this diff to remove the redundant cast:

-    await generator.generate(result.model as Model, outputDir);
+    await generator.generate(result.model, outputDir);
packages/cli/src/actions/action-utils.ts (1)

55-63: Silent failure on parse errors may hide issues.

When there are lexer or parser errors (lines 61-62), the function returns an empty array without logging or warning. This could silently skip plugin loading when the schema has syntax errors, making it harder to diagnose issues.

Consider logging a warning or returning the parse errors so callers can decide how to handle them.

Apply this approach to improve diagnostics:

-    // balk if there are syntax errors
     if (parseResult.lexerErrors.length > 0 || parseResult.parserErrors.length > 0) {
+        // Log parse errors but continue without plugins to allow validation to surface the errors
+        console.warn(colors.yellow('Warning: Schema has syntax errors, skipping plugin discovery'));
         return [];
     }
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2298fc9 and 275af89.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (27)
  • .vscode/launch.json (1 hunks)
  • packages/cli/src/actions/action-utils.ts (3 hunks)
  • packages/cli/src/actions/generate.ts (2 hunks)
  • packages/cli/src/constants.ts (1 hunks)
  • packages/language/res/stdlib.zmodel (0 hunks)
  • packages/language/src/index.ts (2 hunks)
  • packages/language/src/utils.ts (1 hunks)
  • packages/language/src/validators/function-invocation-validator.ts (1 hunks)
  • packages/plugins/policy/package.json (2 hunks)
  • packages/plugins/policy/plugin.zmodel (1 hunks)
  • packages/plugins/policy/src/column-collector.ts (1 hunks)
  • packages/plugins/policy/src/policy-handler.ts (2 hunks)
  • packages/runtime/src/index.ts (1 hunks)
  • packages/runtime/src/utils/schema-utils.ts (1 hunks)
  • packages/sdk/src/index.ts (0 hunks)
  • packages/sdk/src/model-utils.ts (1 hunks)
  • packages/testtools/src/client.ts (2 hunks)
  • packages/testtools/src/schema.ts (5 hunks)
  • packages/testtools/src/utils.ts (1 hunks)
  • samples/blog/main.ts (2 hunks)
  • samples/blog/package.json (1 hunks)
  • samples/blog/zenstack/models.ts (0 hunks)
  • samples/blog/zenstack/schema.ts (3 hunks)
  • samples/blog/zenstack/schema.zmodel (3 hunks)
  • tests/e2e/orm/policy/migrated/current-model.test.ts (1 hunks)
  • tests/e2e/orm/policy/migrated/current-operation.test.ts (1 hunks)
  • tests/e2e/orm/scripts/generate.ts (2 hunks)
💤 Files with no reviewable changes (3)
  • packages/language/res/stdlib.zmodel
  • samples/blog/zenstack/models.ts
  • packages/sdk/src/index.ts
🧰 Additional context used
📓 Path-based instructions (4)
{packages,samples,tests}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place packages only under packages/, samples/, or tests/

Files:

  • packages/runtime/src/index.ts
  • packages/cli/src/constants.ts
  • tests/e2e/orm/policy/migrated/current-model.test.ts
  • packages/runtime/src/utils/schema-utils.ts
  • packages/plugins/policy/package.json
  • packages/testtools/src/schema.ts
  • packages/language/src/validators/function-invocation-validator.ts
  • packages/testtools/src/client.ts
  • tests/e2e/orm/policy/migrated/current-operation.test.ts
  • packages/plugins/policy/src/column-collector.ts
  • packages/language/src/index.ts
  • samples/blog/package.json
  • packages/plugins/policy/plugin.zmodel
  • packages/testtools/src/utils.ts
  • samples/blog/zenstack/schema.ts
  • packages/cli/src/actions/action-utils.ts
  • packages/plugins/policy/src/policy-handler.ts
  • packages/language/src/utils.ts
  • packages/cli/src/actions/generate.ts
  • samples/blog/zenstack/schema.zmodel
  • samples/blog/main.ts
  • tests/e2e/orm/scripts/generate.ts
  • packages/sdk/src/model-utils.ts
tests/e2e/**

📄 CodeRabbit inference engine (CLAUDE.md)

End-to-end tests must live under tests/e2e/

Files:

  • tests/e2e/orm/policy/migrated/current-model.test.ts
  • tests/e2e/orm/policy/migrated/current-operation.test.ts
  • tests/e2e/orm/scripts/generate.ts
**/schema.ts

📄 CodeRabbit inference engine (CLAUDE.md)

The generated TypeScript schema should be named schema.ts

Files:

  • packages/testtools/src/schema.ts
  • samples/blog/zenstack/schema.ts
**/schema.zmodel

📄 CodeRabbit inference engine (CLAUDE.md)

Name ZModel schema files schema.zmodel

Files:

  • samples/blog/zenstack/schema.zmodel
🧠 Learnings (1)
📚 Learning: 2025-09-04T12:38:14.150Z
Learnt from: CR
PR: zenstackhq/zenstack-v3#0
File: CLAUDE.md:0-0
Timestamp: 2025-09-04T12:38:14.150Z
Learning: Applies to **/schema.ts : The generated TypeScript schema should be named `schema.ts`

Applied to files:

  • tests/e2e/orm/scripts/generate.ts
🧬 Code graph analysis (10)
packages/testtools/src/schema.ts (1)
packages/testtools/src/utils.ts (1)
  • loadDocumentWithPlugins (4-7)
packages/language/src/validators/function-invocation-validator.ts (2)
packages/language/src/generated/ast.ts (6)
  • DataModelAttribute (395-400)
  • DataModelAttribute (402-402)
  • DataFieldAttribute (348-353)
  • DataFieldAttribute (355-355)
  • isDataModelAttribute (404-406)
  • isDataFieldAttribute (357-359)
packages/language/src/utils.ts (2)
  • getFunctionExpressionContext (333-347)
  • getLiteral (268-282)
packages/testtools/src/client.ts (1)
packages/testtools/src/utils.ts (1)
  • loadDocumentWithPlugins (4-7)
packages/testtools/src/utils.ts (1)
packages/language/src/index.ts (1)
  • loadDocument (21-136)
samples/blog/zenstack/schema.ts (1)
packages/runtime/src/schema/expression.ts (1)
  • ExpressionUtils (16-120)
packages/cli/src/actions/action-utils.ts (5)
packages/language/src/index.ts (2)
  • createZModelServices (11-13)
  • loadDocument (21-136)
packages/language/src/module.ts (1)
  • ZModelServices (31-31)
packages/language/src/generated/ast.ts (1)
  • isPlugin (628-630)
packages/language/src/utils.ts (1)
  • getLiteral (268-282)
packages/cli/src/constants.ts (1)
  • PLUGIN_MODULE_NAME (5-5)
packages/language/src/utils.ts (2)
packages/language/src/ast.ts (2)
  • AstNode (4-4)
  • AstNode (66-71)
packages/language/src/generated/ast.ts (1)
  • isInvocationExpr (542-544)
packages/cli/src/actions/generate.ts (1)
packages/sdk/src/cli-plugin.ts (1)
  • CliPlugin (32-47)
samples/blog/main.ts (1)
packages/plugins/policy/src/plugin.ts (1)
  • PolicyPlugin (6-29)
tests/e2e/orm/scripts/generate.ts (1)
packages/language/src/index.ts (1)
  • loadDocument (21-136)
🔇 Additional comments (30)
packages/sdk/src/model-utils.ts (1)

16-16: LGTM! Proper use of type-only import.

The change to a type-only import is correct since Model is exclusively used in type annotation positions (getContainingModel return type on Line 90 and getAuthDecl parameter type on Line 104). This follows TypeScript best practices and can improve build performance.

samples/blog/package.json (1)

18-18: LGTM!

The addition of the @zenstackhq/plugin-policy workspace dependency correctly enables the plugin-based policy architecture introduced in this PR.

.vscode/launch.json (1)

14-14: LGTM!

The working directory adjustment aligns with the refactored project structure, removing the nested zenstack subdirectory requirement for CLI debugging.

packages/plugins/policy/src/policy-handler.ts (2)

273-273: LGTM!

The switch to SchemaUtils.ExpressionVisitor correctly uses the runtime-provided base class, maintaining consistency with the plugin architecture refactor.


10-10: No issues found. SchemaUtils and ExpressionVisitor are properly exported.

Verification confirms:

  • SchemaUtils is exported from @zenstackhq/runtime as a namespace (packages/runtime/src/index.ts:3)
  • ExpressionVisitor is exported from the schema-utils module (packages/runtime/src/utils/schema-utils.ts:15)
  • The import chain is complete and the reference to SchemaUtils.ExpressionVisitor will resolve correctly
packages/plugins/policy/src/column-collector.ts (2)

7-7: LGTM!

The base class change to KyselyUtils.DefaultOperationNodeVisitor correctly aligns with the runtime utilities migration.


1-1: KyselyUtils export is properly configured and correctly imported.

Verification confirms that KyselyUtils is correctly exported from @zenstackhq/runtime and the import in column-collector.ts is valid. The DefaultOperationNodeVisitor class is properly accessible via the namespace, and the entire export chain is intact.

packages/plugins/policy/package.json (2)

16-17: LGTM!

The plugin.zmodel file inclusion correctly packages the policy DSL definitions for distribution.


30-33: LGTM!

The export entry for ./plugin.zmodel is correctly configured, making the policy DSL file accessible to consumers of the plugin package.

samples/blog/zenstack/schema.ts (3)

72-75: LGTM!

The auto-generated policy attributes for the User model correctly express:

  • Unconditional read/create access
  • Full access when authenticated as this user

The expression structure using ExpressionUtils is correct.


139-141: LGTM!

The auto-generated policy attribute for the Profile model correctly uses the check function to delegate access control to the related user field.


198-201: LGTM!

The auto-generated policy attributes for the Post model correctly express:

  • Public read access when published
  • Full access for the post author
packages/plugins/policy/plugin.zmodel (2)

1-26: LGTM!

The @@allow and @@deny attribute definitions are well-documented with:

  • Clear parameter descriptions
  • Comprehensive completion hints covering all operations (create, read, update, post-update, delete, all)
  • Proper type annotations

42-72: LGTM!

The helper functions (check, before, currentModel, currentOperation) are well-defined with:

  • Clear documentation and parameter descriptions
  • Proper @@@expressionContext annotations limiting usage to AccessPolicy contexts
  • Support for optional parameters (operation casing, etc.)
packages/runtime/src/utils/schema-utils.ts (1)

13-13: LGTM!

The import path adjustment from ./schema to ../schema correctly reflects the module structure reorganization in the runtime package.

tests/e2e/orm/policy/migrated/current-model.test.ts (1)

161-161: LGTM: Proper addition of @id directive.

The addition of the @id directive aligns the test model with proper schema conventions. This makes the test case more realistic and complete.

packages/language/src/index.ts (1)

54-58: LGTM: Clear variable rename improves readability.

Renaming pluginDocs to additionalDocs is more accurate since this parameter can accept any additional model files, not just plugin-specific documents. The change is consistent across both declaration and usage.

Also applies to: 72-72

packages/runtime/src/index.ts (1)

2-3: Verify stability of newly exposed utilities.

You're expanding the public API surface by exporting KyselyUtils and SchemaUtils as namespaces. Ensure these utilities:

  1. Have stable interfaces suitable for public consumption
  2. Are properly documented
  3. Follow semantic versioning for any future changes

Once these utilities are public, breaking changes will affect external consumers.

packages/cli/src/constants.ts (1)

4-5: LGTM: Constant standardizes plugin model naming.

Introducing PLUGIN_MODULE_NAME as a constant is a good practice that enables consistent plugin model file discovery across the codebase.

samples/blog/main.ts (1)

1-1: LGTM: Clear demonstration of plugin-based policy feature.

The addition demonstrates the new plugin-based policy architecture effectively:

  • Proper PolicyPlugin integration via $use()
  • Authorization context establishment with $setAuth()
  • Clear demonstration of policy-aware reads for different users

This sample code provides a good reference for users adopting the new policy plugin.

Also applies to: 94-103

packages/cli/src/actions/generate.ts (1)

67-67: LGTM: Proper type widening and guard addition.

The type change to CliPlugin | undefined and the corresponding guard at lines 86-88 correctly handle plugins that don't export generators. This allows the system to gracefully skip non-generator plugins.

Also applies to: 86-88

packages/testtools/src/client.ts (1)

17-17: LGTM: Aligns with plugin-aware document loading.

The change from loadDocument to loadDocumentWithPlugins correctly integrates plugin model resolution during the Prisma push path. Based on the code snippet from packages/testtools/src/utils.ts, this function wraps loadDocument with plugin model paths.

Note: The implementation in utils.ts appears to use a hardcoded path similar to the concern raised in tests/e2e/orm/scripts/generate.ts. Ensure consistency in how plugin models are resolved across the codebase.

Also applies to: 119-119

tests/e2e/orm/scripts/generate.ts (1)

22-26: Review comment is incorrect. The hardcoded path is appropriate for this test script context.

The original suggestion to use getPluginDocuments misidentifies the use case. getPluginDocuments is an async function that discovers plugins by parsing schema declarations and requires ZModelServices (a full language server setup). The hardcoded path here provides a known plugin to loadDocument, which is the correct pattern for a standalone test utility script without language services.

The same hardcoded path pattern is used consistently throughout packages/testtools/src/utils.ts for identical scenarios, and this approach is deterministic in npm workspaces where node_modules location is fixed. No changes needed.

Likely an incorrect or invalid review comment.

packages/language/src/utils.ts (1)

450-453: LGTM! Aligns with plugin-based refactor.

The removal of the isFromStdlib check aligns with the broader refactoring to move policy-related functionality to a plugin architecture. The TODO comment appropriately flags this for future migration.

tests/e2e/orm/policy/migrated/current-operation.test.ts (1)

130-130: LGTM! Corrects test schema.

Adding the @id attribute ensures the test schema is valid and properly tests the constraint that currentOperation() cannot be used outside policy contexts.

packages/testtools/src/schema.ts (1)

12-12: LGTM! Consistent migration to plugin-aware loading.

The import change and subsequent usage throughout the file consistently migrates from loadDocument to loadDocumentWithPlugins, enabling plugin policy integration in test tooling.

packages/language/src/validators/function-invocation-validator.ts (1)

54-76: LGTM! Generalizes validation for plugin architecture.

The refactoring removes the stdlib-only restriction, making validation context computation unconditional. This aligns with the broader plugin-based architecture where policy functions can come from plugins rather than only stdlib.

The logic correctly:

  • Finds the containing attribute for any invocation
  • Computes expression context regardless of origin
  • Validates function context constraints consistently
samples/blog/zenstack/schema.zmodel (1)

31-32: LGTM! Demonstrates policy plugin usage.

The access rules correctly demonstrate the new plugin-based policy feature with @@allow declarations using auth() for user authentication context.

packages/cli/src/actions/action-utils.ts (1)

95-103: LGTM! ESM module resolution provides fallback.

The ESM module resolution using import.meta.resolve provides a robust fallback for resolving plugin modules from node_modules, complementing the file-based resolution strategies.

packages/testtools/src/utils.ts (1)

4-7: Verify plugin path resolution robustness and test coverage for workspace dependency.

The hardcoded path ../node_modules/@zenstackhq/plugin-policy/plugin.zmodel is incompatible with workspace dependency resolution. Since @zenstackhq/plugin-policy is declared as a workspace:* dependency in packages/testtools/package.json, its location depends on the package manager and monorepo configuration—not always in node_modules. Additionally, there is no file existence validation before passing the path to loadDocument.

The production approach exists in packages/cli/src/actions/action-utils.ts where getPluginDocuments dynamically discovers plugins by:

  • Parsing the model file to extract plugin declarations
  • Validating paths with fs.existsSync()
  • Returning empty array on parse errors (graceful degradation)

Manually verify:

  1. Whether tests exercise scenarios where the plugin is resolved differently (pnpm, yarn, different build output directories)
  2. Whether the current hardcoded path reliably resolves in CI/CD and local development environments
  3. Whether adopting dynamic discovery or adding existence checks is appropriate here

@claude
Copy link

claude bot commented Oct 17, 2025

Claude finished @ymc9's task —— View job


PR Review: Move Policy Attributes to Own ZModel

I've completed a comprehensive review of this PR that moves policy attributes from the core stdlib.zmodel to a separate plugin-specific plugin.zmodel file. This is an excellent architectural improvement.

Review Tasks:

  • Understand the refactoring scope and architecture changes
  • Review the new plugin.zmodel structure
  • Analyze CLI integration changes
  • Check runtime and SDK modifications
  • Evaluate test coverage and sample updates
  • Assess code quality, bugs, performance, and security

Positive Aspects

1. Excellent Separation of Concerns

  • Clean Architecture: Moving policy-specific attributes (@@allow, @@deny) and functions (check, before, currentModel, currentOperation) to a dedicated plugin module is a great design decision
  • Modular Design: This change enables the policy functionality to be truly plugin-based rather than hardcoded in the core language

2. Proper Plugin Discovery Mechanism

  • Smart Resolution: The CLI now properly discovers plugin.zmodel files via multiple strategies:
    • Local file path resolution
    • ESM module resolution (import.meta.resolve)
    • Upward directory traversal (findUp)
  • Good File Structure: Uses PLUGIN_MODULE_NAME = 'plugin.zmodel' constant for consistency

3. Comprehensive Integration

  • Package.json Export: The policy plugin properly exports the plugin.zmodel file in packages/plugins/policy/package.json:30-33
  • Test Infrastructure: Updated test utilities (loadDocumentWithPlugins) to include plugin models
  • Sample Updates: Blog sample correctly demonstrates the new plugin usage

🔍 Code Quality Analysis

1. Implementation Quality

  • Error Handling: Good error handling in getPluginDocuments() at packages/cli/src/actions/action-utils.ts:98-103
  • Validation: Proper syntax error checking before processing plugins (action-utils.ts:61-63)
  • Type Safety: Maintains strong typing throughout the refactoring

2. Documentation and Comments

  • Good Comments: Policy functions have clear JSDoc documentation in plugin.zmodel
  • Context Validation: Function validation properly handles policy-specific context (function-invocation-validator.ts:229-305)

⚠️ Areas of Concern

1. Hard-coded Path Dependencies

// packages/testtools/src/utils.ts:5
const pluginModelFiles = [path.resolve(__dirname, '../node_modules/@zenstackhq/plugin-policy/plugin.zmodel')];

// tests/e2e/orm/scripts/generate.ts:26
const pluginDocs = [path.resolve(_dirname, '../../node_modules/@zenstackhq/plugin-policy/plugin.zmodel')];

Issue: These hard-coded paths are brittle and will break if:

  • Plugin is installed in a different location
  • Multiple versions of the plugin exist
  • Plugin is loaded from a different package manager structure

Recommendation: Use the same dynamic discovery mechanism from getPluginDocuments() instead of hard-coded paths.

2. Missing Error Cases

  • No validation for when plugin.zmodel file exists but contains syntax errors
  • Limited feedback when plugin discovery fails silently (action-utils.ts:100-102)

3. Function Validation Location

The check() function validation is still in the core language validator (function-invocation-validator.ts:229-305) with a TODO comment:

// TODO: move this to policy plugin
@func('check')

This creates a dependency from core to policy plugin concepts.


🚀 Performance Considerations

Positive

  • Plugin discovery only happens during schema loading, not runtime
  • File system operations are properly cached/memoized where needed
  • No runtime performance impact

Areas for Improvement

  • Multiple file system operations during plugin discovery could be optimized
  • Consider caching resolved plugin paths

🔒 Security Analysis

Safe Practices

  • ✅ No direct file path injection vulnerabilities
  • ✅ Proper validation of plugin provider values
  • ✅ Safe ESM module resolution with try-catch

Potential Concerns

  • Plugin loading via import() could theoretically load arbitrary code, but this is by design and expected behavior
  • File system traversal is properly constrained

📝 Test Coverage

Comprehensive Updates

  • ✅ E2E tests updated to use new plugin system
  • ✅ Policy-specific tests maintained functionality
  • ✅ Sample applications demonstrate correct usage

Missing Coverage

  • No specific tests for plugin discovery failure scenarios
  • Limited testing of edge cases in getPluginDocuments()

🎯 Recommendations

  1. Fix Hard-coded Paths (High Priority)

    • Replace hard-coded plugin paths in test utilities with dynamic discovery
    • Use the CLI's getPluginDocuments() logic consistently
  2. Move Policy Validation (Medium Priority)

    • Complete the TODO by moving check() function validation to the policy plugin
    • This would remove the circular dependency
  3. Improve Error Handling (Medium Priority)

    • Add better error messages when plugin files are malformed
    • Provide clearer feedback when plugin discovery fails
  4. Add Tests (Low Priority)

    • Test plugin discovery edge cases
    • Test behavior with malformed plugin.zmodel files

Overall Assessment

This is a well-executed refactoring that significantly improves the architecture by:

  • Making policy functionality truly modular
  • Enabling better plugin ecosystem development
  • Maintaining backward compatibility
  • Following good separation of concerns principles

The implementation is largely sound with only a few minor issues that should be addressed. The code quality is high and the changes are well-integrated across the codebase.

Recommendation: ✅ Approve with minor fixes

The hard-coded path dependencies should be addressed, but they don't prevent the PR from being merged as they're isolated to test infrastructure.


Copy link

@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

🧹 Nitpick comments (1)
packages/plugins/policy/plugin.zmodel (1)

17-17: Consider removing or documenting commented-out field-level attributes.

Lines 17 and 33 contain commented-out declarations for field-level @allow and @deny. If these are planned for future implementation, add a TODO comment explaining the intent. Otherwise, remove them to reduce clutter.

Also applies to: 33-33

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 275af89 and de4d670.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (2)
  • packages/language/test/utils.ts (2 hunks)
  • packages/plugins/policy/plugin.zmodel (1 hunks)
🧰 Additional context used
📓 Path-based instructions (1)
{packages,samples,tests}/**

📄 CodeRabbit inference engine (CLAUDE.md)

Place packages only under packages/, samples/, or tests/

Files:

  • packages/language/test/utils.ts
  • packages/plugins/policy/plugin.zmodel
🧬 Code graph analysis (1)
packages/language/test/utils.ts (2)
packages/testtools/src/schema.ts (1)
  • loadSchema (94-124)
packages/language/src/index.ts (1)
  • loadDocument (21-136)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: claude-review
  • GitHub Check: build-test (20.x, sqlite)
  • GitHub Check: build-test (20.x, postgresql)
🔇 Additional comments (3)
packages/language/test/utils.ts (2)

8-8: LGTM! Path construction is correct.

The path correctly resolves to packages/plugins/policy/plugin.zmodel from the test directory.


14-14: LGTM! Plugin documents correctly integrated.

The plugin documents are properly passed to loadDocument in both test utilities, aligning with the updated function signature.

Also applies to: 28-28

packages/plugins/policy/plugin.zmodel (1)

35-72: LGTM! Function declarations are well-documented.

The check, before, currentModel, and currentOperation functions are clearly documented with appropriate expression context annotations and parameter descriptions.

@ymc9 ymc9 merged commit 59dfa73 into dev Oct 17, 2025
7 checks passed
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