feat(auth): LogicalModel scope for Method ACL and FieldRule - #259
Conversation
- Add LogicalModelName (and Method LogicalMethods) as a fifth exclusive scope so one rule covers per-app isomorphic inject models across host apps. - Wire CheckMethodAccess, FieldRule eval, and PermissionState ACL aggregation; register short names from platform inject bases; seed bootstrap grants and admin scope UI. Co-authored-by: Cursor <cursoragent@cursor.com>
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
📝 WalkthroughWalkthroughThis change adds registered logical models as authorization scopes. It supports logical method and field rules, evaluates them in ACL paths, exposes them in administration views, adds bootstrap permissions, and applies repository authorization bypasses to selected internal operations. ChangesLogical-model ACL support
Estimated code review effort: 4 (Complex) | ~45 minutes Sequence Diagram(s)sequenceDiagram
participant Admin
participant RoleMethodAccessFormView
participant RoleMethodAccess
participant evaluateRoleMethodAccess
participant PermissionStateACL
Admin->>RoleMethodAccessFormView: Select logical model and methods
RoleMethodAccessFormView->>RoleMethodAccess: Submit logical scope rule
evaluateRoleMethodAccess->>RoleMethodAccess: Query logical-model rules
RoleMethodAccess-->>evaluateRoleMethodAccess: Return matching method permissions
PermissionStateACL->>RoleMethodAccess: Load logical model and method data
PermissionStateACL-->>Admin: Expose effective permission state
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (4)
modules/auth/web/views/access_rules_field_binding.test.ts (2)
37-46: 📐 Maintainability & Code Quality | 🔵 TrivialRun the required validation checks before merge.
Build the CLI and generate ignored embedded assets before installing modules. Then run the affected auth module typecheck and unit tests, plus applicable auth E2E tests. Confirm that application code remains compatible with embedded QuickJS.
As per coding guidelines, these checks are required for frontend modules and the auth module.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/auth/web/views/access_rules_field_binding.test.ts` around lines 37 - 46, Before merging changes to the access-rules form bindings tested by “PR-LM-4”, build the CLI and generate ignored embedded assets before installing modules. Then run the auth module typecheck, unit tests, applicable auth E2E tests, and the compatibility validation for embedded QuickJS.Source: Coding guidelines
37-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtend this binding test to cover the new UI contracts.
The test checks the logical property names, but it does not verify
:allow-array="true"inmodules/auth/web/views/RoleMethodAccessFormView.vueor the newLogicalModelNamecolumns inmodules/auth/web/views/RoleMethodAccessListView.vueandmodules/auth/web/views/RoleFieldRuleListView.vue. Add assertions for these bindings. The array binding is part of the contract defined bymodules/auth/service/models/_logical_model_registry.ts:52-82.Suggested assertions
expect(methodForm).toContain('prop="LogicalMethods"'); + expect(methodForm).toContain(':allow-array="true"'); expect(methodForm).toContain('Logical Model (all host apps sharing that short name)'); + expect(viewSource('RoleMethodAccessListView.vue')).toContain('prop="LogicalModelName"'); + expect(viewSource('RoleFieldRuleListView.vue')).toContain('prop="LogicalModelName"');🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/auth/web/views/access_rules_field_binding.test.ts` around lines 37 - 46, Extend the test case around viewSource in access_rules_field_binding.test.ts to assert RoleMethodAccessFormView.vue includes the :allow-array="true" binding, and assert RoleMethodAccessListView.vue and RoleFieldRuleListView.vue each include the new LogicalModelName column. Preserve the existing property and label assertions.modules/auth/service/models/_rule_scope_helpers.ts (1)
29-30: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value
supportsLogicalModelis never read.
assertExclusiveScopebranches on the field keyLogicalModelNameat Line 145, not onspec.supportsLogicalModel. The flag is therefore unused metadata. Either use it in the normalization branch or drop it to avoid two sources of truth.♻️ Option: drive normalization from the flag
- if (f === 'LogicalModelName') { + if (f === 'LogicalModelName' && spec.supportsLogicalModel) { ids[f] = normalizeLogicalModelName((values as any)[f]); } else { ids[f] = normalizeRefId((values as any)[f]); }Also applies to: 145-149
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/auth/service/models/_rule_scope_helpers.ts` around lines 29 - 30, Resolve the duplicate source of truth between supportsLogicalModel and the LogicalModelName key check in assertExclusiveScope. Prefer using spec.supportsLogicalModel to drive the normalization branch, replacing the hardcoded field-key condition while preserving existing behavior; alternatively remove the unused flag and its related metadata.modules/auth/service/tests/permission_state_acl_source.test.ts (1)
270-277: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAdd a case for a logical rule without
LogicalMethods.The current test covers the method-restricted path only. The
methods == nullbranch setsallowAlland emits therpc:/<app>.<Model>/*wildcard. That branch is untested.💚 Suggested extra assertions
expect(allows.has('rpc:/auth.User/*')).toBe(false); + + (RoleMethodAccess as any).Search = async () => [ + { + RoleId: 'role_1', + MetaServiceId: null, + MetaModelId: null, + MetaApplicationId: null, + LogicalModelName: 'FieldDefault', + LogicalMethods: null, + Mode: 'allow', + Source: 'manual', + }, + ]; + const aggAll = await buildAclAggregation(['role_1'], { role_1: { global: true, companies: [] } }); + const allowsAll = aggAll.requiresAllowKeysByCompany.get('*') || new Set(); + expect(allowsAll.has('rpc:/auth.FieldDefault/*')).toBe(true); + expect(aggAll.companyGlobalAllow.has('*')).toBe(false);🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@modules/auth/service/tests/permission_state_acl_source.test.ts` around lines 270 - 277, Extend the aggregation test around buildAclAggregation to include a logical rule whose methods value is null or omitted. Assert that the resulting allows include the rpc:/<app>.<Model>/* wildcard for that rule, covering the allowAll branch while preserving the existing method-specific assertions.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modules/auth/data/bootstrap.json`:
- Around line 76-104: Add a RoleRecordRule grant for the FieldDefault model to
the auth.role_base_user permissions in bootstrap.json, granting the required
write and create access used by FieldDefault.Set. Keep the existing
rma_base_user_field_default_logical and rfr_base_user_field_default_logical
grants unchanged.
In `@modules/auth/service/models/_user_permission_state_acl.ts`:
- Around line 194-199: Handle malformed LogicalMethods consistently per ACL
path: in modules/auth/service/models/_user_permission_state_acl.ts at lines
194-199, preserve the deny row and treat a normalization failure as model-wide;
in modules/auth/service/models/_user_method_access.ts at lines 160-169, catch
normalization errors per row and skip only that invalid rule so method checks do
not fail globally. Update the surrounding ACL projection and method-access
filtering logic without changing valid payload handling.
In `@modules/auth/service/models/role_method_access.ts`:
- Around line 176-195: Update the LogicalModelName handling in the role-method
validation flow to prevent stale LogicalMethods when the scope changes between
logical models. In the update path around touchesLogicalName, detect a changed
non-empty LogicalModelName and either clear LogicalMethods when it is omitted or
require a replacement whitelist in the same payload, while preserving existing
behavior for unchanged scope and non-logical rows.
---
Nitpick comments:
In `@modules/auth/service/models/_rule_scope_helpers.ts`:
- Around line 29-30: Resolve the duplicate source of truth between
supportsLogicalModel and the LogicalModelName key check in assertExclusiveScope.
Prefer using spec.supportsLogicalModel to drive the normalization branch,
replacing the hardcoded field-key condition while preserving existing behavior;
alternatively remove the unused flag and its related metadata.
In `@modules/auth/service/tests/permission_state_acl_source.test.ts`:
- Around line 270-277: Extend the aggregation test around buildAclAggregation to
include a logical rule whose methods value is null or omitted. Assert that the
resulting allows include the rpc:/<app>.<Model>/* wildcard for that rule,
covering the allowAll branch while preserving the existing method-specific
assertions.
In `@modules/auth/web/views/access_rules_field_binding.test.ts`:
- Around line 37-46: Before merging changes to the access-rules form bindings
tested by “PR-LM-4”, build the CLI and generate ignored embedded assets before
installing modules. Then run the auth module typecheck, unit tests, applicable
auth E2E tests, and the compatibility validation for embedded QuickJS.
- Around line 37-46: Extend the test case around viewSource in
access_rules_field_binding.test.ts to assert RoleMethodAccessFormView.vue
includes the :allow-array="true" binding, and assert
RoleMethodAccessListView.vue and RoleFieldRuleListView.vue each include the new
LogicalModelName column. Preserve the existing property and label assertions.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 293fff57-2078-406c-bfaa-2e74eb5a9390
📒 Files selected for processing (25)
modules/auth/data/bootstrap.jsonmodules/auth/service/models/_logical_model_registry.tsmodules/auth/service/models/_rule_scope_helpers.tsmodules/auth/service/models/_user_field_rule_eval.tsmodules/auth/service/models/_user_lifecycle_auth.tsmodules/auth/service/models/_user_method_access.tsmodules/auth/service/models/_user_permission_state_acl.tsmodules/auth/service/models/role_field_rule.tsmodules/auth/service/models/role_method_access.tsmodules/auth/service/models/user.tsmodules/auth/service/tests/field_rule.test.tsmodules/auth/service/tests/logical_model_registry.test.tsmodules/auth/service/tests/method_access_eval_observability.test.tsmodules/auth/service/tests/permission_state_acl_source.test.tsmodules/auth/service/tests/rule_scope_helpers.test.tsmodules/auth/web/views/RoleFieldRuleFormView.vuemodules/auth/web/views/RoleFieldRuleListView.vuemodules/auth/web/views/RoleMethodAccessFormView.vuemodules/auth/web/views/RoleMethodAccessListView.vuemodules/auth/web/views/access_rules_field_binding.test.tsmodules/core/service/orm/model/app_setting_base_model.tsmodules/core/service/orm/model/field_default_base_model.tsmodules/core/service/orm/model/logical_model_registry.test.tsmodules/core/service/orm/model/logical_model_registry.tsmodules/core/service/orm/model/translation_term_base_model.ts
There was a problem hiding this comment.
All reported issues were addressed across 25 files
Tip: instead of fixing issues one by one fix them all with cubic
Re-trigger cubic
- Reuse auth's loginAsE2EAdmin helper so Login submit waits for nprogress and Login RPC, matching meta and avoiding stuck /web/login redirects. Co-authored-by: Cursor <cursoragent@cursor.com>
- Fail closed on malformed LogicalMethods (deny keeps/model-wide; allow skips) and reject non-string whitelist entries. - Clear stale LogicalMethods when LogicalModelName changes; allow methods-only updates; drop unused supportsLogicalModel. - Bypass RecordRule/FieldRule on FieldDefault store Get/Set/Unset paths so preset Logical Method grants work without O(apps) RR seeds. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
All reported issues were addressed across 10 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
- Cover LogicalMethods normalize/Onchange/FieldsGet, malformed fail-closed paths, and Logical FieldRule eval. - Exercise remaining registry and method-access branch edges Codecov reported as partials. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
All reported issues were addressed across 5 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
- Require an explicit LogicalMethods payload when LogicalModelName is updated so clearing the whitelist cannot silently widen an allow. - Restore Logical Model help-text assertions and tighten empty-modelName FieldRule deny-default expectations. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modules/auth/service/tests/logical_model_acl_coverage.test.ts`:
- Around line 187-200: Update the ACL coverage tests around the rejected rename
and LogicalMethods validation to read the row after each attempted update and
assert that LogicalModelName and LogicalMethods remain equal to their pre-update
values. Preserve the existing rejection-message and rejection assertions while
verifying failed updates do not alter the stored logical scope or whitelist.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: c78a6a1c-eab0-4840-8994-0cfa9924b78a
📒 Files selected for processing (3)
modules/auth/service/models/role_method_access.tsmodules/auth/service/tests/logical_model_acl_coverage.test.tsmodules/auth/web/views/access_rules_field_binding.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
- modules/auth/web/views/access_rules_field_binding.test.ts
- modules/auth/service/models/role_method_access.ts
There was a problem hiding this comment.
All reported issues were addressed across 3 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
- Gate the rename fail-closed throw on an actual LogicalModelName change versus the persisted value so Mode toggles that re-echo scope still succeed. - Assert rejected renames leave stored scope/methods unchanged and methods-only updates keep LogicalModelName. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
All reported issues were addressed across 2 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
- Prove every matched row already has the target LogicalModelName via Count equality instead of a capped Search sample. - Fail closed when any matched row is non-logical or has a different name so null whitelist cannot widen allow. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@modules/auth/service/tests/logical_model_acl_coverage.test.ts`:
- Around line 304-312: Update the mixedAfter query and assertions around
RoleMethodAccess.Search to include Mode in the requested fields, then assert
that both the logical-model row and service row retain Mode equal to allow after
the rejected bulk update.
🪄 Autofix
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro
Run ID: 92cc66fe-716a-4156-82ed-a669a25a474f
📒 Files selected for processing (2)
modules/auth/service/models/role_method_access.tsmodules/auth/service/tests/logical_model_acl_coverage.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- modules/auth/service/models/role_method_access.ts
- Verify both mixed-scope rows keep Mode=allow when a no-methods LogicalModelName bulk update is rejected. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
2 issues found across 2 files (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="modules/auth/service/models/role_method_access.ts">
<violation number="1" location="modules/auth/service/models/role_method_access.ts:280">
P2: Bulk reaffirm validation checks a different row set when callers use `withDeleted` or `onlyDeleted`, allowing an unvalidated logical-model rename or rejecting a valid reaffirm. Passing the same `options` to both `Count` calls would keep validation aligned with `super.Update`.</violation>
<violation number="2" location="modules/auth/service/models/role_method_access.ts:284">
P1: A concurrent update can change a matched row's logical scope after these counts, so this call may perform a real rename without an explicit `LogicalMethods` whitelist and retain methods from the intervening scope. The proof should be coupled atomically to locked rows, or the final update condition should also require `LogicalModelName === next` so raced rows are skipped safely.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Fix all with cubic | Re-trigger cubic
- Pass Update options into both Count proofs so withDeleted/onlyDeleted match the write set. - Narrow the update condition to LogicalModelName === next after a successful reaffirm proof so concurrent scope races are skipped instead of silently renamed. Co-authored-by: Cursor <cursoragent@cursor.com>
- Cover nullish _needsPreviousLogicalModelName, Count=0 bulk updates, non-logical bulk renames, and missing UpdateById rows. - Let Create omit Source so the Field default factory runs; still coerce Source=ui to manual when present. Co-authored-by: Cursor <cursoragent@cursor.com>
User description
Summary
RoleMethodAccess/RoleFieldRuleso one grant covers per-app isomorphic inject models (TranslationTerm/FieldDefault/AppSetting) across all host apps.CheckMethodAccess, FieldRule, PermissionState ACL aggregation), core base-class self-registration for logical names, bootstrap preset grants, and admin Method/Field scope UI (selection + Onchange exclusivity).withRepositoryAuthzRuleBypass(e.g. LoginAppSetting.Get); do not use Logical grants as data-plane sudo.Test plan
./choysum test unit auth --be./choysum test unit auth --fe./choysum test unit core --be(logical model registry)terminology.editor/base.user/sys.adminterminology.editor,Search/Updateonbase.TranslationTerm(non-auth host) is allowed;GetTranslationsstill works via internal path without user Logical grantMade with Cursor
Summary by cubic
Adds a LogicalModel scope to
RoleMethodAccessandRoleFieldRuleso one rule covers per‑app inject models across all host apps. Updates runtime, admin UI, and seeds; rebuild the DB or reinstall modules to apply new grants.New Features
LogicalModelNameas a fifth, exclusive scope; optionalLogicalMethodswhitelist (empty/null = all).AppSetting,FieldDefault,TranslationTerm.LogicalMethods; field rules resolve in order Field > Model > Application > Logical Model > Global; ACL aggregation expands logical grants to matching app models and allowed methods.LogicalMethods; Onchange enforces exclusive scopes. Seeds add logical grants forterminology.editor(TranslationTerm),base.user(FieldDefault), andsys.admin(AppSetting).Bug Fixes
LogicalMethods: malformed payloads fail closed; non‑string entries rejected; stale whitelists cleared when the logical model changes; renames require methods only when the name actually changes; reaffirming the same name is allowed; methods‑only updates keep the name; rejected updates leave stored scope/mode unchanged.LogicalModelNameto prevent racey renames.AppSetting.GetandFieldDefaultstoreGet/Set/Unsetuse narrow repository‑level bypass so logical grants work without per‑app RR/FR seeds. E2E smoke reusesloginAsE2EAdminto avoid auth init races.RoleMethodAccess.CreateletsSourcedefault via the field factory;uiis coerced tomanualwhen provided.Written for commit 75936a8. Summary will update on new commits.
PR Type
Enhancement
Description
Go core (outside modules/): No changes in this PR.
TypeScript modules: Added LogicalModel scope to RoleMethodAccess and RoleFieldRule.
Registered AppSetting, FieldDefault, and TranslationTerm in core logical model registry.
Updated runtime evaluation, ACL aggregation, bootstrap grants, and admin views.
Verified SPDX headers on all 4 new source files; expanded unit test coverage.
File Walkthrough
13 files
Create process-local registry for logical model namesRegister AppSetting as a logical model short nameRegister FieldDefault as a logical model short nameRegister TranslationTerm as a logical model short nameProvide auth helpers for logical model normalization and matchingSupport LogicalModelName in exclusive scope validationEvaluate LogicalModel scoped rules in field rule evaluationIncorporate LogicalModel scope into method access resolutionAggregate LogicalModel ACL grants into permission stateAdd LogicalModelName field and Onchange clear handlersAdd LogicalModelName and LogicalMethods fields with payloadnormalizationExpose LogicalModel selection in Field Rule form viewExpose LogicalModel and LogicalMethods in Method Access form view1 files
Update seed bootstrap grants to use LogicalModel scope6 files
Add tests for core logical model registry self-registrationAdd unit tests for auth logical model registry helpersUpdate scope helper tests for LogicalModel shapesAdd test for LogicalModel scope in ACL aggregationAdd method access evaluation test for logical methods whitelistVerify LogicalModel field bindings in admin Vue views5 files
Summary by CodeRabbit