Skip to content

Conversation

@ymc9
Copy link
Member

@ymc9 ymc9 commented Sep 23, 2025

Summary by CodeRabbit

  • New Features
    • Expanded access policy expressions with allow/deny rules at model and field levels, plus a check() function for permission queries.
  • Refactor
    • Consolidated policy capabilities into the standard library, removing the need for a separate runtime plugin.
    • Simplified project setup and code generation; plugin discovery is no longer required.
  • Chores
    • Streamlined build by removing post-build file copying.
    • Updated internal tooling and tests to use simplified document loading.

Copilot AI review requested due to automatic review settings September 23, 2025 05:30
@coderabbitai
Copy link

coderabbitai bot commented Sep 23, 2025

Walkthrough

Moved policy DSL declarations (allow/deny/check) from runtime plugin to language stdlib; restored future() in AccessPolicy. Updated loadDocument API to accept only schema path and removed plugin model discovery across tests, tooling, and scripts. Deleted runtime policy plugin file and removed related build copy hook.

Changes

Cohort / File(s) Summary of changes
Language stdlib: policy API exposed
packages/language/res/stdlib.zmodel
Added @@Allow, @Allow (with optional override), @@deny, @deny attributes and check(field, operation?) function; ensured completion hints; restored AccessPolicy.future().
Runtime policy plugin removed
packages/runtime/src/plugins/policy/plugin.zmodel
Deleted policy DSL (allow/deny/check) declarations from runtime plugin.
Build config cleanup
packages/runtime/tsup.config.ts
Removed fs import and onSuccess copy hook for policy plugin file.
Testtools schema API updates
packages/testtools/src/schema.ts
Removed getPluginModules and glob usage; updated generateTsSchema and generateTsSchemaInPlace to call loadDocument(schemaPath) only; simplified return of in-place generation.
Language tests update
packages/language/test/utils.ts
Dropped plugin model discovery; adjusted loadDocument invocation to single-arg form.
Runtime tests update
packages/runtime/test/utils.ts, packages/runtime/test/scripts/generate.ts
Removed getPluginModules/imports and plugin model file handling; updated loadDocument(schemaPath).
Regression script update
tests/regression/generate.ts
Simplified to loadDocument(schemaPath); removed plugin model resolution and related variables.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A rabbit taps keys with delight today,
Allow and deny hop a brand-new way.
Plugins burrowed; stdlib took the lead,
One-arg loading—swift as a steed.
Policies checked, fields shining bright,
Future restored—onward we write! 🐇✨

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 title "refactor: move policy zmodel back to stdlib" is concise and accurately captures the primary change in this PR — policy DSL declarations were removed from the runtime plugin and reintroduced in packages/language/res/stdlib.zmodel while being removed from packages/runtime/src/plugins/policy/plugin.zmodel. It is a single clear sentence that communicates the main refactor intent without noisy details. This phrasing is appropriate for a teammate scanning PR history.
✨ Finishing touches
  • 📝 Generate Docstrings
🧪 Generate unit tests
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch refactor/policy-zmodel-stdlib

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.

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

This PR refactors the policy zmodel definitions by moving them from the runtime package plugin system back to the language package's standard library. This simplifies the architecture by eliminating the need for plugin-specific model files and their associated loading mechanisms.

  • Removes plugin-based policy model loading infrastructure
  • Consolidates policy attributes and functions into the main stdlib.zmodel
  • Simplifies document loading by removing plugin model file parameters

Reviewed Changes

Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/regression/generate.ts Removes plugin model file loading from schema generation
packages/testtools/src/schema.ts Eliminates plugin module resolution and removes glob dependency
packages/runtime/tsup.config.ts Removes build step that copied plugin.zmodel files
packages/runtime/test/utils.ts Updates test utilities to use simplified document loading
packages/runtime/test/scripts/generate.ts Removes plugin model file loading from test script
packages/runtime/src/plugins/policy/plugin.zmodel Deletes the standalone plugin model file
packages/language/test/utils.ts Removes plugin model loading from language tests
packages/language/res/stdlib.zmodel Adds all policy attributes and functions to the standard library

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

@claude
Copy link

claude bot commented Sep 23, 2025

Claude finished @ymc9's task —— View job


PR Review: Move Policy ZModel Back to Stdlib

Reviewing this architectural refactoring that moves policy zmodel definitions from the runtime plugin system to the language stdlib.

Review Tasks:

  • Read and analyze changed files
  • Review architectural changes and code quality
  • Check for potential bugs or issues
  • Assess performance implications
  • Evaluate security considerations
  • Review test coverage impact
  • Provide comprehensive feedback

✅ Overall Assessment

This is a well-executed architectural simplification that consolidates policy-related ZModel definitions from a separate plugin system into the main standard library. The refactoring reduces complexity while maintaining functionality.

📋 Detailed Review

Architectural Changes - Excellent

  • Simplification: Moving from plugin-based loading to stdlib inclusion is a smart architectural decision
  • Consolidation: All policy attributes (@@allow, @@deny, @allow, @deny) and functions (check(), future()) are now centrally located in packages/language/res/stdlib.zmodel:661-713
  • Reduced complexity: Eliminates the need for dynamic plugin model file discovery and loading

Code Quality - Good

  • Consistent Documentation: All moved definitions retain proper JSDoc comments with parameter descriptions
  • Type Safety: Proper type annotations and expression context decorators are preserved
  • Convention Adherence: Follows existing stdlib patterns with @@@expressionContext and completion hints

Potential Issues - Minor

  1. Function Relocation: The future() function was moved from line 120 to line 710 in stdlib.zmodel, which could be confusing but is functionally correct

  2. Removed Dependencies: The changes properly remove glob dependency from test utilities (packages/language/test/utils.ts:2) and plugin loading logic

  3. Build Configuration: packages/runtime/tsup.config.ts cleanly removes the plugin copy step that's no longer needed

Performance Implications - Positive

  • Startup Performance: Eliminates runtime glob pattern matching for plugin discovery
  • Memory Usage: Reduces overhead from separate plugin model loading
  • Load Time: Faster document loading without plugin model resolution

Security Considerations - Good

  • Access Control: All policy-related functions properly maintain @@@expressionContext([AccessPolicy]) restrictions
  • No Security Regressions: Policy enforcement logic remains intact and properly scoped

Test Coverage Impact - Needs Verification

⚠️ Recommendation: Verify that policy-related tests still pass, particularly:

  • Tests that verify @@allow/@@deny attribute functionality
  • Tests using check() and future() functions in access policies
  • Integration tests that depend on policy resolution

The test utility changes look correct, but comprehensive test verification is recommended.

Breaking Changes

  • None Expected: This should be a transparent refactoring for end users
  • API Compatibility: All policy attributes and functions remain available with identical signatures

🎯 Recommendations

  1. Run Full Test Suite: Ensure all policy-related tests pass after this refactoring
  2. Documentation Update: Consider updating any documentation that references plugin-based policy loading
  3. Migration Path: For any external plugins that might have depended on separate policy models, provide guidance

✅ Conclusion

This is a solid architectural improvement that simplifies the codebase without sacrificing functionality. The refactoring is well-executed with proper attention to preserving existing APIs and behavior. The consolidation of policy definitions into the standard library makes the system more maintainable and performant.

Recommendation: ✅ Approve - This change improves the architecture and should be merged after confirming test suite passes.


@claude
Copy link

claude bot commented Sep 23, 2025

Claude finished @ymc9's task —— View job


PR Review: Move Policy ZModel Back to Stdlib

Reviewing this architectural refactoring that moves policy zmodel definitions from the runtime plugin system to the language stdlib.

Review Tasks:

  • Read and analyze changed files
  • Review architectural changes and code quality
  • Check for potential bugs or issues
  • Assess performance implications
  • Evaluate security considerations
  • Review test coverage impact
  • Provide comprehensive feedback

Overall Assessment: ✅ APPROVED - Well-executed refactoring with positive architectural benefits

This PR successfully consolidates policy-related ZModel definitions from a plugin-based architecture back to the language stdlib, resulting in cleaner code and reduced complexity.

Code Quality & Architecture 🏗️

Strengths:

  • Simplified Architecture: Eliminates the plugin loading mechanism for policy definitions, reducing system complexity
  • Better Cohesion: Policy attributes and functions now reside in the main stdlib where they logically belong
  • Consistent Implementation: All ZModel standard library elements are now in one place (packages/language/res/stdlib.zmodel)

Clean Code Changes:

  • Proper removal of plugin infrastructure in tsup.config.ts (lines 8-12 removed)
  • Consistent updates across all test utilities and schema generation scripts
  • No dead code or unused imports left behind

Bug Prevention & Issues 🐛

✅ No Issues Found:

  • All plugin model file references properly removed from loadDocument calls
  • Function signatures simplified consistently across the codebase
  • No breaking changes to public APIs

Potential Future Consideration:

  • The PLUGIN_MODULE_NAME constant in packages/language/src/constants.ts:26 and the isFromPlugin() method in packages/sdk/src/prisma/prisma-schema-generator.ts:278 are still present but appear unused after this change. Consider cleaning these up in a follow-up PR.

Performance

Improvements:

  • Reduced I/O: Eliminates file system operations for loading separate plugin model files
  • Faster Build Times: Removes need to copy plugin.zmodel files during build (tsup config cleanup)
  • Simplified Document Loading: Single stdlib file load instead of multiple plugin file loads

Security 🔒

✅ No Security Concerns:

  • Changes are purely architectural refactoring
  • No sensitive data handling or permission changes
  • Standard library consolidation reduces attack surface by simplifying file loading logic

Test Coverage 🧪

Maintained Coverage:

  • All test utilities properly updated to remove plugin model file parameters
  • No regression in test functionality expected
  • Test schema generation scripts cleaned up consistently

Files Updated Correctly:

  • packages/runtime/test/utils.ts - Removed plugin file loading
  • packages/language/test/utils.ts - Simplified loadDocument calls
  • packages/testtools/src/schema.ts - Removed glob dependency and plugin resolution
  • Test generation scripts updated appropriately

Migration Quality 📦

Excellent Content Migration:
The policy definitions were moved completely and correctly:

  • @@allow and @allow attributes ✅
  • @@deny and @deny attributes ✅
  • check() function ✅
  • future() function ✅
  • auth() function (already in stdlib) ✅

Recommendations

  1. Follow-up Cleanup: Consider removing unused constants and methods related to plugin loading in a future PR
  2. Documentation: Update any documentation that references the plugin-based policy loading if it exists

Summary

This is a well-executed architectural simplification that improves the codebase by:

  • Reducing complexity in the plugin system
  • Improving build performance
  • Consolidating related functionality
  • Maintaining full backward compatibility

The changes are consistent, complete, and maintain all existing functionality while simplifying the overall architecture.


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: 0

🧹 Nitpick comments (13)
packages/language/test/utils.ts (2)

12-12: Single‑arg loadDocument: OK; clean up temp file

Avoid leaking temp files. Wrap in try/finally and unlink after parsing.

 export async function loadSchema(schema: string) {
   // create a temp file
   const tempFile = path.join(os.tmpdir(), `zenstack-schema-${crypto.randomUUID()}.zmodel`);
-  fs.writeFileSync(tempFile, schema);
-  const r = await loadDocument(tempFile);
+  fs.writeFileSync(tempFile, schema);
+  let r;
+  try {
+      r = await loadDocument(tempFile);
+  } finally {
+      fs.existsSync(tempFile) && fs.unlinkSync(tempFile);
+  }

25-25: Mirror the same temp cleanup here

Apply the same try/finally unlink pattern to loadSchemaWithError.

 export async function loadSchemaWithError(schema: string, error: string | RegExp) {
   // create a temp file
   const tempFile = path.join(os.tmpdir(), `zenstack-schema-${crypto.randomUUID()}.zmodel`);
-  fs.writeFileSync(tempFile, schema);
-  const r = await loadDocument(tempFile);
+  fs.writeFileSync(tempFile, schema);
+  let r;
+  try {
+      r = await loadDocument(tempFile);
+  } finally {
+      fs.existsSync(tempFile) && fs.unlinkSync(tempFile);
+  }

Ensure your test runtime is Node ≥18 so global crypto.randomUUID() is available.

packages/language/res/stdlib.zmodel (3)

708-712: Fix future() doc: “before” → “after” update

Name implies post‑update value; current text says “before”.

-/**
- * Gets entities value before an update. Only valid when used in a "update" policy rule.
- */
+/**
+ * Gets the entity's value after the update (the new value). Only valid inside an "update" policy rule.
+ */
 function future(): Any {
 } @@@expressionContext([AccessPolicy])

671-680: Align @Allow(field) docs vs allowed operations

Doc says “read or update”, but completion hints include create/delete/all. Either restrict hints or broaden the doc.

-attribute @allow(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean, _ override: Boolean?)
+attribute @allow(_ operation: String @@@completionHint(["'read'", "'update'"]), _ condition: Boolean, _ override: Boolean?)

If create/delete are intentionally supported at field level, update the doc comment accordingly instead.


689-696: Align @deny(field) similarly

Same inconsistency as @Allow(field).

-attribute @deny(_ operation: String @@@completionHint(["'create'", "'read'", "'update'", "'delete'", "'all'"]), _ condition: Boolean)
+attribute @deny(_ operation: String @@@completionHint(["'read'", "'update'"]), _ condition: Boolean)
packages/runtime/test/scripts/generate.ts (1)

22-22: LGTM on single‑arg loadDocument; surface warnings for visibility

Optional: log non‑fatal warnings to ease triage.

-const result = await loadDocument(schemaPath);
+const result = await loadDocument(schemaPath);
+if (result.success && result.warnings.length) {
+    console.warn(`[loadDocument warnings] ${schemaPath}:`, result.warnings);
+}
packages/runtime/test/utils.ts (1)

116-116: LGTM; consider logging load warnings

When falling back to loadDocument, propagate warnings for easier debugging.

-const r = await loadDocument(path.join(workDir, 'schema.zmodel'));
+const r = await loadDocument(path.join(workDir, 'schema.zmodel'));
+if (r.success && r.warnings.length) {
+    console.warn('[loadDocument warnings]:', r.warnings);
+}
packages/testtools/src/schema.ts (6)

59-61: Unify return shape of helpers (include model in in-place variant or document divergence)

Today generateTsSchema returns { workDir, schema, model } while generateTsSchemaInPlace returns { workDir, schema }. Prefer consistent APIs to avoid caller surprises.

If you want parity, adjust as below:

-    return compileAndLoad(workDir);
+    return { ...(await compileAndLoad(workDir)), model: result.model };

Also applies to: 86-88


69-71: Make dynamic import path cross‑platform (Windows-safe)

ESM import of absolute Windows paths can fail; use pathToFileURL.

Apply:

+import { pathToFileURL } from 'node:url';
@@
-    const module = await import(path.join(workDir, 'schema.js'));
+    const module = await import(pathToFileURL(path.join(workDir, 'schema.js')).href);

Also applies to: 1-1


51-56: Prevent path escape in extraSourceFiles

Guard against ../ escaping workDir even if test-only.

-            const filePath = path.resolve(workDir, `${fileName}.ts`);
+            const filePath = path.resolve(workDir, `${fileName}.ts`);
+            if (!filePath.startsWith(workDir + path.sep)) {
+                throw new Error(`Invalid extraSourceFiles entry: ${fileName}`);
+            }
             fs.mkdirSync(path.dirname(filePath), { recursive: true });
             fs.writeFileSync(filePath, content);

40-41: Make prelude detection more robust

Avoid false positives from comments/strings.

-    const noPrelude = schemaText.includes('datasource ');
+    const noPrelude = /\bdatasource\s+\w+\s*\{/.test(schemaText);

44-46: Surface load warnings to aid debugging

Emit result.warnings so tests reveal potential issues early.

     if (!result.success) {
         throw new Error(`Failed to load schema from ${zmodelPath}: ${result.errors}`);
     }
+    if (result.warnings?.length) {
+        console.warn(`Schema warnings:\n- ${result.warnings.join('\n- ')}`);
+    }

And similarly for the in-place path:

     if (!result.success) {
         throw new Error(`Failed to load schema from ${schemaPath}: ${result.errors}`);
     }
+    if (result.warnings?.length) {
+        console.warn(`Schema warnings:\n- ${result.warnings.join('\n- ')}`);
+    }

Also applies to: 82-84


20-27: Prefer postgresql:// in datasource URL (matches Prisma docs/examples)

Prisma accepts both schemes, but docs and examples use and recommend postgresql:// — update default to match.

-    url = '${dbUrl ?? 'postgres://postgres:postgres@localhost:5432/db'}'
+    url = '${dbUrl ?? 'postgresql://postgres:postgres@localhost:5432/db'}'
📜 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 ba735ae and ed6ecdf.

📒 Files selected for processing (8)
  • packages/language/res/stdlib.zmodel (1 hunks)
  • packages/language/test/utils.ts (2 hunks)
  • packages/runtime/src/plugins/policy/plugin.zmodel (0 hunks)
  • packages/runtime/test/scripts/generate.ts (1 hunks)
  • packages/runtime/test/utils.ts (2 hunks)
  • packages/runtime/tsup.config.ts (0 hunks)
  • packages/testtools/src/schema.ts (2 hunks)
  • tests/regression/generate.ts (1 hunks)
💤 Files with no reviewable changes (2)
  • packages/runtime/tsup.config.ts
  • packages/runtime/src/plugins/policy/plugin.zmodel
🧰 Additional context used
📓 Path-based instructions (2)
{packages,samples,tests}/**

📄 CodeRabbit inference engine (CLAUDE.md)

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

Files:

  • packages/runtime/test/scripts/generate.ts
  • tests/regression/generate.ts
  • packages/runtime/test/utils.ts
  • packages/language/test/utils.ts
  • packages/testtools/src/schema.ts
  • packages/language/res/stdlib.zmodel
**/schema.ts

📄 CodeRabbit inference engine (CLAUDE.md)

The generated TypeScript schema should be named schema.ts

Files:

  • packages/testtools/src/schema.ts
🧠 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:

  • packages/runtime/test/scripts/generate.ts
  • tests/regression/generate.ts
  • packages/testtools/src/schema.ts
🧬 Code graph analysis (5)
packages/runtime/test/scripts/generate.ts (1)
packages/language/src/index.ts (1)
  • loadDocument (21-132)
tests/regression/generate.ts (1)
packages/language/src/index.ts (1)
  • loadDocument (21-132)
packages/runtime/test/utils.ts (1)
packages/language/src/index.ts (1)
  • loadDocument (21-132)
packages/language/test/utils.ts (1)
packages/language/src/index.ts (1)
  • loadDocument (21-132)
packages/testtools/src/schema.ts (1)
packages/language/src/index.ts (1)
  • loadDocument (21-132)
⏰ 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). (2)
  • GitHub Check: build-test (20.x, postgresql)
  • GitHub Check: build-test (20.x, sqlite)
🔇 Additional comments (4)
packages/runtime/test/utils.ts (1)

5-5: Import change looks good

Switching to generateTsSchema simplifies setup.

tests/regression/generate.ts (1)

21-21: Approve — no remaining two-arg loadDocument callers found.

Scanned repo: all loadDocument usages pass a single argument and no getPluginModels/getPluginModules usages were found. Locations: packages/cli/src/actions/action-utils.ts; packages/language/src/index.ts; packages/language/test/import.test.ts; packages/language/test/utils.ts; packages/runtime/test/scripts/generate.ts; packages/runtime/test/utils.ts; packages/testtools/src/schema.ts; tests/regression/generate.ts.

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

81-81: LGTM: consistent update to new loadDocument() signature

Matches the change above.


43-43: LGTM: switched to stdlib-only loadDocument()

Call site now matches the simplified API. Ripgrep returned "No files were searched" — re-run to confirm no lingering plugin-discovery calls using one of these:

git grep -n -e 'getPluginModules' -e 'pluginModelFiles' || true

rg -n -C2 -S --hidden --no-ignore --glob '!/node_modules/' 'getPluginModules|pluginModelFiles' || true

@ymc9 ymc9 merged commit 9bf6d7f into dev Sep 23, 2025
6 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