Skip to content

feat(lifecycle): persist raw declarations and recompute effective models - #245

Merged
buke merged 14 commits into
mainfrom
feat/meta-effective-dual-store-eds2
Aug 5, 2026
Merged

feat(lifecycle): persist raw declarations and recompute effective models#245
buke merged 14 commits into
mainfrom
feat/meta-effective-dual-store-eds2

Conversation

@buke

@buke buke commented Aug 4, 2026

Copy link
Copy Markdown
Contributor

User description

Summary

  • EDS-2 write path: Persist installs declaration-only rows into meta_raw_*, then RecomputeKeys rebuilds unique effective meta_model* projections (stable ids); parent-chain materialize is no longer written to DB.
  • Callers retargeted: Uninstall deletes raw + recomputes (removes rebindMetaModelDataTips); FieldDefault/AppSetting supersede and i18n ensure write/delete raw then recompute; schema migrator/FK path resolve read raw by module/path.
  • Tests: Partner/bank/commercial union + id-stability fixture via metaeff; builder/lifecycle/FD·AS/i18n/schema coverage updated for dual-store.

Test plan

  • go test ./pkg/meta/ ./internal/module/metaeff/ ./internal/module/lifecycle/ ./internal/module/artifact/build/backend/ ./internal/i18n/models/ ./internal/module/evolution/schema/ -count=1
  • Wipe/reinstall partner + partner_bank + partner_commercial and confirm one effective Partner with union fields and stable id across uninstall of commercial/bank
  • Handwritten FieldDefault/AppSetting supersede removes virtual meta_raw_* and recomputes effective
  • Schema migrate still DDL-sources per-module raw declarations

Depends on #244 (EDS-1). Unlocks EDS-3/4 readers + ModelData.

Made with Cursor


PR Type

Enhancement, Tests


Description

  • Persist raw declarations into meta_raw_* tables

    • Go Core: Module persistence now saves raw declaration entities.
    • Effective model projections are recomputed without DB parent-chain materialize.
  • Introduce incremental effective catalog recompute logic

    • Added pkg/meta/recompute.go with LGPL-3.0 SPDX headers.
    • Added internal/module/metaeff/recompute.go wrapper with LGPL-3.0 SPDX headers.
  • Update module uninstaller and callers for dual-store

    • Retargeted AppSetting, FieldDefault, i18n, and uninstaller to raw tables.
    • Schema evolution migrators now read DDL sources from raw declarations.
  • Expand Go test coverage across core packages

    • Added Go unit tests in pkg/meta, metaeff, lifecycle, and schema suites.
    • No TypeScript module changes (modules/).

File Walkthrough

Relevant files
Enhancement
9 files
recompute.go
Implement catalog recomputation and raw persistence helper methods
+319/-0 
recompute.go
Expose incremental recomputation wrapper package with SPDX headers
+23/-0   
builder.go
Update module builder to persist raw models and recompute
+69/-27 
uninstaller.go
Retarget module uninstaller to raw entities and recompute keys
+41/-78 
app_setting.go
Retarget AppSetting loading and supersede logic to raw entities
+17/-14 
field_default.go
Retarget FieldDefault loading and supersede logic to raw entities
+17/-14 
i18n_meta.go
Register I18n declarations on raw tables and recompute effective
+72/-44 
migrator.go
Fetch raw model declarations for schema migration filtering
+4/-3     
foreignkey.go
Resolve target foreign key models from raw model declarations
+6/-2     
Tests
10 files
recompute_test.go
Add Go unit tests for effective catalog recomputation       
+165/-0 
recompute_test.go
Add fixture test for partner extension model recompute     
+145/-0 
builder_test.go
Adapt builder backend unit tests for dual-store tables     
+37/-31 
uninstaller_clean_models_test.go
Update cleanModels error branch unit tests for raw tables
+188/-134
uninstaller_model_data_test.go
Update uninstaller tests for raw declaration deletion and recompute
+34/-222
i18n_meta_test.go
Update i18n metadata unit tests for dual-store raw tables
+48/-46 
app_setting_test.go
Update AppSetting builder tests to seed raw models             
+29/-20 
field_default_test.go
Update FieldDefault builder tests to seed raw models         
+29/-20 
migrator_test.go
Update schema migrator tests to create raw model fixtures
+15/-7   
helpers_test.go
Add raw field helper functions for schema evolution tests
+26/-1   
Documentation
1 files
dual_store_migrate.go
Update dual-store migration comments for EDS-2 persist behavior
+2/-3     
Additional files
5 files
app_setting_coverage_test.go +43/-37 
field_default_coverage_test.go +48/-36 
foreignkey_test.go +2/-2     
bundles_app_setting_test.go +3/-3     
bundles_field_default_test.go +3/-3     

Summary by CodeRabbit

  • Improvements

    • Improved model inheritance so fields and services merge consistently while preserving child overrides.
    • Enhanced metadata updates during module installation, upgrades, and removal, including reliable cleanup and regeneration.
    • Preserved model and service identities across metadata refreshes.
    • Strengthened schema migration and dependency validation error handling.
    • Improved cross-application model references and virtual metadata handling.
    • Improved end-to-end tooling to use the configured workspace version consistently.
  • Tests

    • Expanded coverage for inheritance, metadata recomputation, lifecycle operations, validation, and failure scenarios.

- Persist module models to meta_raw_* only and recompute effective projections without parent-chain materialize.

- Retarget uninstall, FieldDefault/AppSetting supersede, i18n ensure, and schema DDL reads to raw, then recompute; remove tip rebind.

Co-authored-by: Cursor <cursoragent@cursor.com>
@coderabbitai

coderabbitai Bot commented Aug 4, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR moves metadata flows to raw declarations plus recomputed effective projections. It updates build, lifecycle, i18n, schema, and runtime consumers to load raw records, expand inheritance, and recompute effective metadata. It also updates tests and helper APIs for the dual-store model.

Changes

Dual-store metadata flow

Layer / File(s) Summary
Declaration and projection engine
pkg/meta/declaration.go, pkg/meta/recompute.go, pkg/meta/extends_expand.go, pkg/meta/dual_store_migrate.go, internal/module/metaeff/*, pkg/meta/*_test.go
Adds declaration catalog queries and upserts, inheritance expansion, effective recomputation, raw-tree persistence, identifier handling, deletion helpers, and integration coverage.
Build and metadata consumers
internal/module/artifact/build/backend/*, internal/module/artifact/generate/*, internal/module/artifact/build/web/*, internal/i18n/models/*, internal/module/evolution/schema/*
Build, generation, web validation, i18n, and schema flows now use declaration or effective catalogs and recompute logical keys after metadata changes.
Lifecycle and runtime updates
internal/module/lifecycle/*, internal/testing/e2e/*, modules/auth/service/*
Lifecycle cleanup and migration now use raw metadata. Playwright resolution uses configured npm roots. Field-rule inputs use modelFullName.

Estimated code review effort: 4 (Complex) | ~60 minutes

Sequence Diagram(s)

sequenceDiagram
  participant ModuleBuilder
  participant RawMetadata
  participant RecomputeKeys
  participant ExpandModelsAlongExtends
  participant EffectiveMetadata
  ModuleBuilder->>RawMetadata: Persist raw model trees
  ModuleBuilder->>RecomputeKeys: Recompute logical keys
  RecomputeKeys->>RawMetadata: Load raw trees
  RecomputeKeys->>ExpandModelsAlongExtends: Expand inherited fields and services
  ExpandModelsAlongExtends->>RawMetadata: Load parent models
  RecomputeKeys->>EffectiveMetadata: Persist effective projection
Loading

Possibly related PRs

Suggested labels: Review effort 4/5

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 13.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the primary lifecycle change: persisting raw declarations and recomputing effective models.
Description check ✅ Passed The description provides a clear summary, detailed scope, test plan, pending checks, dependencies, and file walkthrough aligned with the pull request objectives.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/meta-effective-dual-store-eds2

Comment @coderabbitai help to get the list of available commands.

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR Reviewer Guide 🔍

Here are some key observations to aid the review process:

🎫 Ticket compliance analysis 🔶

244 - Partially compliant

Compliant requirements:

  • Persist declaration-only rows into meta_raw_* tables during module persistence without parent-chain materialize into meta_model.
  • Recompute effective model projections incrementally via RecomputeKeys to maintain one unique effective projection per (application, name).
  • Retarget callers (uninstaller, AppSetting, FieldDefault, i18n, schema migrator, FK resolver) to read/write raw tables and trigger recomputations.
  • Add unit test coverage for dual-store persistence and recomputation lifecycle.

Non-compliant requirements:

None

Requires further human verification:

None

⏱️ Estimated effort to review: 3 🔵🔵🔵⚪⚪
🧪 PR contains tests
🔒 No security concerns identified
⚡ No major issues detected

@github-actions

github-actions Bot commented Aug 4, 2026

Copy link
Copy Markdown

PR Code Suggestions ✨

No code suggestions found for the PR.

@codecov

codecov Bot commented Aug 4, 2026

Copy link
Copy Markdown

Comment thread internal/i18n/models/i18n_meta.go
buke and others added 2 commits August 4, 2026 20:53
- Mint raw tree IDs and expand Extends before schema migrate / E2 recompute so thin subclasses keep inherited columns and services.

- Omit Models on install/upgrade Module.Save and clear in-memory trees after persist so declaration shells no longer duplicate effective rows.

- Read codegen models by application name and prefer empty-module_id rows in UI rpc dependency checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Reuse tip service ids by method name when rewriting the effective subtree so Auth ACL rows keyed by meta_service_id stay valid.

- Stop deleting legacy module_id effective rows before recompute so EDS5 can keep the tip model id.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 6

🧹 Nitpick comments (9)
internal/module/evolution/schema/migrator_test.go (1)

105-116: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover inheritance expansion in the schema-loading test.

The fixture contains only standalone raw models. It verifies raw preloads and filtering, but not the new RawModelsAsModels plus ExpandModelsAlongExtends path. Add an abstract base model with a field and a child model with Extends, then assert that the loaded child contains the inherited field.

🤖 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 `@internal/module/evolution/schema/migrator_test.go` around lines 105 - 116,
The schema-loading fixture in the test should cover inheritance expansion, not
only standalone models. Extend the rawModels setup with an abstract base model
containing a field and a child model referencing it through Extends, then assert
after loading and expansion that the child includes the inherited field while
preserving existing filtering assertions.
internal/module/artifact/generate/generator_test.go (1)

348-387: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Exercise the effective projection path in the canonical-model test.

All models in this fixture have a valid ModuleId. The test can pass with the old module join. Add a separate model with Application: "crm" and an empty ModuleId, then assert that getApplication() loads it.

🤖 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 `@internal/module/artifact/generate/generator_test.go` around lines 348 - 387,
Extend the canonical-model fixture near the existing model variables with a
separate model whose Application is “crm” and ModuleId is empty, then include it
in the persisted test data. Update the canonical-model assertions to call
getApplication() for this model and verify it is loaded, exercising the
effective projection path independently of the module join.
pkg/meta/dual_store_migrate_coverage_test.go (1)

595-600: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add coverage for the non-nil reuse map.

Every persistEffectiveProjection call in this file passes nil for reuseServiceIDs, so the reuse branch and the !usedServiceIDs[prev] duplicate guard in dual_store_migrate.go lines 544-549 stay uncovered. TestRecomputeEffective_PreservesServiceIDsByName exercises the happy path through RecomputeEffective, but not the case where two merged services share a trimmed name and only one can take the prior id.

Add one direct case here with a reuse map and two same-named services. Assert that the second service receives a fresh id.

🤖 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 `@pkg/meta/dual_store_migrate_coverage_test.go` around lines 595 - 600, Extend
the direct persistEffectiveProjection coverage with a non-nil reuseServiceIDs
map and two merged services sharing the same trimmed name; verify the first
service reuses the prior ID and the second receives a fresh ID, covering the
!usedServiceIDs[prev] duplicate guard without changing existing failure
assertions.
pkg/meta/extends_expand_test.go (1)

65-85: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the no-write invariant.

ExpandModelsAlongExtends documents that it does not write to the database, and cloneFieldShallow/cloneServiceShallow clear BaseModel and foreign keys to keep that true. The test does not verify it. Add row counts on Model, Field, and Service after expansion, and assert the cloned entities carry empty ids.

♻️ Proposed additional assertions
 	if !svcNames["Create"] || !svcNames["Normalize"] {
 		t.Fatalf("unexpected services: %#v", svcNames)
 	}
+	for _, f := range child.Fields {
+		if f.Id.Valid && f.Id.String != "" {
+			t.Fatalf("expanded field must stay unpersisted: %#v", f)
+		}
+	}
+	for _, tbl := range []any{&Model{}, &Field{}, &Service{}} {
+		var n int64
+		if err := db.Model(tbl).Count(&n).Error; err != nil {
+			t.Fatalf("count: %v", err)
+		}
+		if n != 0 {
+			t.Fatalf("expansion must not write effective rows, got %d", n)
+		}
+	}
 }
🤖 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 `@pkg/meta/extends_expand_test.go` around lines 65 - 85, Add no-write invariant
checks to the test around ExpandModelsAlongExtends: capture Model, Field, and
Service row counts before expansion and assert they are unchanged afterward,
then verify expanded cloned fields and services have empty IDs. Keep the
existing field and service name assertions intact.
pkg/meta/dual_store_migrate.go (2)

536-550: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document why prior service-id reuse cannot collide.

The reuse branch assigns a prior effective service id to a newly created row. This is only safe because RecomputeEffective calls DeleteEffectiveModelTree for every existing row before it calls persistEffectiveProjection, so the ids are free at insert time. A future caller that passes a non-nil reuseServiceIDs without deleting first would hit a primary-key conflict.

Add that precondition to the doc comment on persistEffectiveProjection and on the reuseServiceIDs parameter.

🤖 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 `@pkg/meta/dual_store_migrate.go` around lines 536 - 550, Update the
documentation for persistEffectiveProjection and its reuseServiceIDs parameter
to state that reuseServiceIDs may only be provided after existing effective rows
have been deleted, as performed by RecomputeEffective via
DeleteEffectiveModelTree before persistEffectiveProjection; otherwise reused IDs
can cause primary-key conflicts.

283-345: 🚀 Performance & Scalability | 🔵 Trivial | ⚖️ Poor tradeoff

Consider batching the raw-tree inserts.

copyModelTreeToRaw issues one Create per field, service, parameter, type parameter, decorator, and argument. A module with several hundred models produces thousands of round trips on every build and on every IMD migration. The ensureBaseModelID change means every row now has its id assigned in Go before insert, so the rows can be collected per entity type and written with CreateInBatches.

This is a performance improvement only; the current code is correct. Defer it if build time is acceptable today.

🤖 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 `@pkg/meta/dual_store_migrate.go` around lines 283 - 345, Defer this optional
performance improvement unless build time requires it; if implemented, update
copyModelTreeToRaw to collect each raw entity type, including fields, services,
parameters, type parameters, decorators, and arguments, after ensureBaseModelID,
then persist each collection with GORM CreateInBatches while preserving existing
relationships and error propagation.
pkg/meta/recompute.go (1)

67-71: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the no-op probe and stop discarding the lock error.

Line 68 runs SELECT 1 and discards the result. It does not serialize anything, and the comment above it suggests otherwise. Delete it.

lockLogicalKey also discards the error from the FOR UPDATE query (line 204) and always returns nil. gorm.ErrRecordNotFound is expected when no effective row exists yet, but a lock timeout, serialization failure, or connection error is silently ignored, and the recompute continues without the lock it believes it holds. Return the non-ErrRecordNotFound errors.

♻️ Proposed cleanup
-	// Serialize concurrent recomputes for the same logical name (best-effort on SQLite).
-	_ = tx.Exec("SELECT 1").Error
+	// Serialize concurrent recomputes for the same logical name (best-effort on SQLite).
 	if err := lockLogicalKey(tx, key); err != nil {
 		return err
 	}
 	case "postgres":
 		// Advisory lock keyed by app+name hash would be ideal; fall back to FOR UPDATE
 		// on any existing effective row (no-op when missing).
 		var m Model
-		_ = tx.Clauses(clause.Locking{Strength: "UPDATE"}).
+		err := tx.Clauses(clause.Locking{Strength: "UPDATE"}).
 			Where("application = ? AND name = ?", key.Application, key.Name).
 			Take(&m).Error
+		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
+			return fmt.Errorf("lock effective %s/%s: %w", key.Application, key.Name, err)
+		}
🤖 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 `@pkg/meta/recompute.go` around lines 67 - 71, Remove the discarded SELECT 1
probe before lockLogicalKey in the recompute flow. Update lockLogicalKey to
propagate errors from its FOR UPDATE query while treating gorm.ErrRecordNotFound
as the expected no-row case; return all other errors so recomputation does not
continue without a valid lock.
internal/module/metaeff/recompute.go (1)

12-23: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The wrapper is bypassed by at least one consumer.

This package forwards to pkg/meta without adding behavior. internal/i18n/models/i18n_meta.go line 58 calls meta.RecomputeEffective directly instead of going through metaeff. Two entry points for the same operation make it harder to add incremental-recompute policy later, which the package doc states is its purpose.

Pick one path. Either route the internal callers through metaeff, or drop the package until it holds real logic.

🤖 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 `@internal/module/metaeff/recompute.go` around lines 12 - 23, Ensure all
internal callers use a single recomputation entry point: update the direct
meta.RecomputeEffective call in i18n metadata handling to go through
metaeff.RecomputeEffective, or remove the pass-through metaeff package and its
wrappers. Preserve the existing recomputation behavior while eliminating
duplicate access paths.
pkg/meta/recompute_test.go (1)

202-229: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

The assertion does not prove deduplication.

RecomputeEffective is idempotent, so recomputing {a, X} once or twice both leave exactly one row. count == 1 therefore holds whether or not RecomputeKeys dedupes. The invalid key {Application: " ", Name: "X"} also produces no row on its own, so it is not observed either.

To test the actual behavior, count the recompute invocations, or assert that the effective row's UpdatedAt/id is untouched by a second pass. Alternatively, assert LogicalKey{Application: " ", Name: "X"}.Valid() is false directly to cover the skip branch.

🤖 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 `@pkg/meta/recompute_test.go` around lines 202 - 229, Strengthen
TestRecomputeKeys_Dedupes so it observes deduplication rather than only the
idempotent final row count: either instrument and assert that the recompute path
runs once for duplicate valid keys, or capture the effective row’s
identity/timestamp and verify the duplicate pass leaves it unchanged. Also add a
direct assertion that LogicalKey{Application: " ", Name: "X"}.Valid() is false
to cover the invalid-key skip branch.
🤖 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 `@internal/i18n/models/i18n_meta.go`:
- Around line 71-73: Update the raw I18n lookup to select by the canonical path
and application rather than logical name, while preserving the existing ordering
behavior. Add a regression test that creates multiple raw I18n declarations for
the same application and verifies recomputation updates or creates the canonical
go://i18n/<application> declaration instead of modifying an extension.

In `@internal/module/artifact/build/backend/builder.go`:
- Around line 330-353: Update the raw extends query in the lookup block before
`extendsRaws` is populated to filter by both application and model name, using
`model.Application` and falling back to `b.module.ApplicationStr` when the model
application is unset. Preserve the existing ordering and conversion logic, and
add a test covering same-name declarations from different applications to ensure
the selected extends path remains within the current application.

In `@internal/module/evolution/schema/foreignkey.go`:
- Around line 148-160: Update resolveTargetModelByPath to constrain the RawModel
query by the target module’s ModuleId as well as path, resolving the owning
module before converting the result so duplicate paths across modules cannot
select the wrong ModelTable. Preserve the existing conversion and empty-result
handling, and add a regression test covering identical paths declared in
different modules.

In `@internal/module/evolution/schema/migrator.go`:
- Around line 46-57: Update newMigrator and all callers of getModuleModels to
handle its returned error before invoking Migrate(); propagate the error from
ExpandModelsAlongExtends rather than constructing a migrator with incomplete
models. Ensure both migrators fail early when model loading or inheritance
expansion fails.

In `@internal/module/metaeff/recompute_test.go`:
- Around line 96-99: The fixture cleanup deletes ignore errors in both recompute
test files. In internal/module/metaeff/recompute_test.go at lines 96-99, check
and fail on errors from every RawField delete at lines 99, 118, and 134,
matching the adjacent RawModel deletes; in pkg/meta/recompute_test.go at lines
103-106, do the same for RawField deletes at lines 106 and 125. Use the existing
test failure pattern so cleanup failures are reported immediately.

In `@pkg/meta/recompute_test.go`:
- Around line 192-199: The loop iterating over second.Services will not execute
if the collection is empty, causing the test to pass without verifying any
service properties. Add an assertion that validates the service count before the
loop over second.Services to ensure the test actually performs the id rotation
check rather than passing vacuously when no services exist.

---

Nitpick comments:
In `@internal/module/artifact/generate/generator_test.go`:
- Around line 348-387: Extend the canonical-model fixture near the existing
model variables with a separate model whose Application is “crm” and ModuleId is
empty, then include it in the persisted test data. Update the canonical-model
assertions to call getApplication() for this model and verify it is loaded,
exercising the effective projection path independently of the module join.

In `@internal/module/evolution/schema/migrator_test.go`:
- Around line 105-116: The schema-loading fixture in the test should cover
inheritance expansion, not only standalone models. Extend the rawModels setup
with an abstract base model containing a field and a child model referencing it
through Extends, then assert after loading and expansion that the child includes
the inherited field while preserving existing filtering assertions.

In `@internal/module/metaeff/recompute.go`:
- Around line 12-23: Ensure all internal callers use a single recomputation
entry point: update the direct meta.RecomputeEffective call in i18n metadata
handling to go through metaeff.RecomputeEffective, or remove the pass-through
metaeff package and its wrappers. Preserve the existing recomputation behavior
while eliminating duplicate access paths.

In `@pkg/meta/dual_store_migrate_coverage_test.go`:
- Around line 595-600: Extend the direct persistEffectiveProjection coverage
with a non-nil reuseServiceIDs map and two merged services sharing the same
trimmed name; verify the first service reuses the prior ID and the second
receives a fresh ID, covering the !usedServiceIDs[prev] duplicate guard without
changing existing failure assertions.

In `@pkg/meta/dual_store_migrate.go`:
- Around line 536-550: Update the documentation for persistEffectiveProjection
and its reuseServiceIDs parameter to state that reuseServiceIDs may only be
provided after existing effective rows have been deleted, as performed by
RecomputeEffective via DeleteEffectiveModelTree before
persistEffectiveProjection; otherwise reused IDs can cause primary-key
conflicts.
- Around line 283-345: Defer this optional performance improvement unless build
time requires it; if implemented, update copyModelTreeToRaw to collect each raw
entity type, including fields, services, parameters, type parameters,
decorators, and arguments, after ensureBaseModelID, then persist each collection
with GORM CreateInBatches while preserving existing relationships and error
propagation.

In `@pkg/meta/extends_expand_test.go`:
- Around line 65-85: Add no-write invariant checks to the test around
ExpandModelsAlongExtends: capture Model, Field, and Service row counts before
expansion and assert they are unchanged afterward, then verify expanded cloned
fields and services have empty IDs. Keep the existing field and service name
assertions intact.

In `@pkg/meta/recompute_test.go`:
- Around line 202-229: Strengthen TestRecomputeKeys_Dedupes so it observes
deduplication rather than only the idempotent final row count: either instrument
and assert that the recompute path runs once for duplicate valid keys, or
capture the effective row’s identity/timestamp and verify the duplicate pass
leaves it unchanged. Also add a direct assertion that LogicalKey{Application: "
", Name: "X"}.Valid() is false to cover the invalid-key skip branch.

In `@pkg/meta/recompute.go`:
- Around line 67-71: Remove the discarded SELECT 1 probe before lockLogicalKey
in the recompute flow. Update lockLogicalKey to propagate errors from its FOR
UPDATE query while treating gorm.ErrRecordNotFound as the expected no-row case;
return all other errors so recomputation does not continue without a valid lock.
🪄 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: 885bf1bc-d9b1-4729-8ebb-6bb057a2b8ab

📥 Commits

Reviewing files that changed from the base of the PR and between 2844af4 and 192fe2d.

📒 Files selected for processing (34)
  • internal/i18n/models/i18n_meta.go
  • internal/i18n/models/i18n_meta_test.go
  • internal/module/artifact/build/backend/app_setting.go
  • internal/module/artifact/build/backend/app_setting_coverage_test.go
  • internal/module/artifact/build/backend/app_setting_test.go
  • internal/module/artifact/build/backend/builder.go
  • internal/module/artifact/build/backend/builder_test.go
  • internal/module/artifact/build/backend/field_default.go
  • internal/module/artifact/build/backend/field_default_coverage_test.go
  • internal/module/artifact/build/backend/field_default_test.go
  • internal/module/artifact/build/web/webBuilder.go
  • internal/module/artifact/generate/generator.go
  • internal/module/artifact/generate/generator_test.go
  • internal/module/evolution/schema/foreignkey.go
  • internal/module/evolution/schema/foreignkey_test.go
  • internal/module/evolution/schema/helpers_test.go
  • internal/module/evolution/schema/migrator.go
  • internal/module/evolution/schema/migrator_test.go
  • internal/module/lifecycle/bundles_app_setting_test.go
  • internal/module/lifecycle/bundles_field_default_test.go
  • internal/module/lifecycle/installer.go
  • internal/module/lifecycle/uninstaller.go
  • internal/module/lifecycle/uninstaller_clean_models_test.go
  • internal/module/lifecycle/uninstaller_model_data_test.go
  • internal/module/lifecycle/upgrader.go
  • internal/module/metaeff/recompute.go
  • internal/module/metaeff/recompute_test.go
  • pkg/meta/dual_store_migrate.go
  • pkg/meta/dual_store_migrate_coverage_test.go
  • pkg/meta/dual_store_migrate_test.go
  • pkg/meta/extends_expand.go
  • pkg/meta/extends_expand_test.go
  • pkg/meta/recompute.go
  • pkg/meta/recompute_test.go

Comment thread internal/i18n/models/i18n_meta.go Outdated
Comment thread internal/module/artifact/build/backend/builder.go Outdated
Comment thread internal/module/evolution/schema/foreignkey.go Outdated
Comment thread internal/module/evolution/schema/migrator.go Outdated
Comment thread internal/module/metaeff/recompute_test.go Outdated
Comment thread pkg/meta/recompute_test.go
buke and others added 2 commits August 4, 2026 21:31
- Add extends_expand and recompute coverage suites, including delete/merge/lock hooks for late error paths.

- Cover i18n/builder/web/schema/lifecycle/app-setting/field-default patch gaps and remove unreachable RawModelsAsModels empty checks.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Select the canonical go://i18n path when seeding I18n raw services.

- Scope extends lookup by application and reject ambiguous FK target tables.

- Propagate schema model-load errors from NewMigrator and lockLogicalKey.

Co-authored-by: Cursor <cursoragent@cursor.com>
@buke

buke commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread internal/i18n/models/i18n_meta.go
buke and others added 3 commits August 4, 2026 21:55
- Exercise FK effective/raw lookup errors and ambiguous tables.

- Hit NewMigrator failures in install/upgrade and lockLogicalKey errors.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Promote the canonical I18n raw UpdatedAt past same-name extensions before recompute.

- Assert effective Path and built-in services stay on go://i18n/<application>.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Force promoteI18nCanonicalTip failure after a successful seed so the EnsureI18nMeta error return is exercised.

Co-authored-by: Cursor <cursoragent@cursor.com>
@buke

buke commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread pkg/meta/recompute.go
- Promote created_at with updated_at so pickTipRaw and E2 tip agree on go://i18n.

- Assert the first effective model id matches the canonical raw id.

Co-authored-by: Cursor <cursoragent@cursor.com>
@buke

buke commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@cursor review

Comment thread pkg/meta/recompute.go
Comment thread pkg/meta/recompute.go

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
internal/module/artifact/build/backend/builder_test.go (1)

712-740: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Assert the effective projection after raw replacement.

Line 712 seeds a stale meta.Model, but the test only checks meta.RawModel after persistModuleModels. A regression that leaves /models/stale in meta_model will pass. Query meta.Model and assert that only the current partner projections remain.

Proposed test assertion
 	if len(persisted) != 2 || persisted[0].Path != "/models/order" || persisted[1].Name != "Partner" {
 		t.Fatalf("unexpected persisted raw models: %#v", persisted)
 	}
+
+	var effective []*meta.Model
+	if err := db.Where("application = ?", "partner").Order("path ASC").Find(&effective).Error; err != nil {
+		t.Fatalf("query effective models: %v", err)
+	}
+	if len(effective) != 2 || effective[0].Path != "/models/order" || effective[1].Path != "/models/partner" || effective[1].Name != "Partner" {
+		t.Fatalf("unexpected effective models: %#v", effective)
+	}
🤖 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 `@internal/module/artifact/build/backend/builder_test.go` around lines 712 -
740, Extend the persistModuleModels test after the existing RawModel assertions
to query meta.Model for module-1 and assert that only the current partner
projections remain, excluding the seeded stale model and nil entry. Keep the
assertion focused on the expected model count and paths/names so regressions
leaving /models/stale in meta_model fail.
internal/i18n/models/i18n_meta.go (1)

51-64: 🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

Serialize canonical raw-model initialization and recomputation.

EnsureI18nMeta does not protect raw-model creation, service registration, tip promotion, recomputation, and ACL seeding as one operation. The (path, module_id) unique index permits duplicate canonical rows when module_id is NULL or differs. Serialize the full (application, i18nModelName) operation and add a concurrent regression test.

🤖 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 `@internal/i18n/models/i18n_meta.go` around lines 51 - 64, Update
EnsureI18nMeta to serialize the entire (application, i18nModelName) workflow,
covering ensureI18nRawModel, ensureI18nRawServices, promoteI18nCanonicalTip,
meta.RecomputeEffective, and ACL seeding under one per-key lock or equivalent.
Preserve existing error propagation while preventing concurrent initialization
from creating duplicate canonical rows, and add a regression test that runs
concurrent initialization for the same application and verifies only one
canonical raw model exists.
🧹 Nitpick comments (2)
pkg/meta/recompute_coverage_test.go (1)

525-530: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove the unreachable branch.

tc.drop holds a *RawModel pointer for the first case. &RawModel{} allocates a new pointer, so the comparison is always true. The else branch never runs, and both branches drop the same table. The comment about drop ordering describes behavior that the code does not implement.

♻️ Proposed simplification
-			if tc.drop != (&RawModel{}) {
-				_ = db2.Migrator().DropTable(tc.drop)
-			} else {
-				// Drop after pluck would need model to exist first; drop before second call.
-				_ = db2.Migrator().DropTable(&RawModel{})
-			}
+			// For the RawModel case this drops the table before the pluck, which
+			// makes the first load fail.
+			_ = db2.Migrator().DropTable(tc.drop)
🤖 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 `@pkg/meta/recompute_coverage_test.go` around lines 525 - 530, Remove the
unreachable else branch in the test cleanup around tc.drop, and always call
db2.Migrator().DropTable(tc.drop). Delete the obsolete drop-ordering comment
while preserving the existing cleanup behavior.
pkg/meta/extends_expand_coverage_test.go (1)

39-90: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

The test does not prove a cache hit.

The assertions only confirm that both children inherit the parent field and service. They also pass when expandShapeAlongExtends reloads the parent from the database for each child. Count the parent loads to assert cache reuse, or rename the test to describe shared-parent inheritance.

🤖 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 `@pkg/meta/extends_expand_coverage_test.go` around lines 39 - 90, Update
TestExpandModelsAlongExtends_CacheHitViaTwoChildren to verify cache reuse by
instrumenting or counting parent loads and asserting the shared parent is loaded
only once while expanding both children. Keep the existing inheritance
assertions, or rename the test to reflect shared-parent inheritance if
load-count instrumentation is not available.
🤖 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 `@internal/module/artifact/build/backend/builder_test.go`:
- Around line 1530-1539: Replace the PRAGMA query_only approach with a
connection-independent failure injection by registering a Before callback on the
"gorm:create" hook for the "meta_raw_model" table in the test. Within the
callback, call tx.AddError to inject the failure that matches the expected error
message "persist raw model". Use t.Cleanup to unregister the callback after the
test completes. This ensures the failure injection applies regardless of which
connection from GORM's pool is used for the persistModuleModels insert
operation.

In `@internal/module/artifact/build/web/webBuilder_test.go`:
- Around line 3764-3798: Reorder the fixture in the “prefers effective model
over declaration shell” test so the shell model is persisted before the
effective model. Keep the existing model and service definitions unchanged,
ensuring the test exercises validation when an unordered first-row lookup
returns the shell.

In `@pkg/meta/recompute_coverage_test.go`:
- Around line 765-774: The test manually restores
mergeSameNameModelsByExtensionChainFn at the end, but if t.Fatalf is called
during the test, line 774 never executes and the stub persists for subsequent
tests. Replace the manual restoration at the end with a t.Cleanup call
(registered after saving prevMerge) that restores
mergeSameNameModelsByExtensionChainFn regardless of whether the test passes or
fails, following the same pattern already established for lockLogicalKeyFn and
expandModelsAlongExtendsFn in this test.
- Around line 50-79: Update the namedDialector-based test setup in
TestRecomputeEffective_PostgresLockBranch and
TestLockLogicalKey_PostgresNonNotFoundError so it exercises the PostgreSQL
branch without emitting SQLite-incompatible FOR UPDATE SQL. Preserve the
postgres Name() result while configuring the wrapped SQLite dialector to omit or
neutralize the locking clause, allowing RecomputeEffective to succeed and the
dropped-table case to reach and return the expected “lock effective row” error.

---

Outside diff comments:
In `@internal/i18n/models/i18n_meta.go`:
- Around line 51-64: Update EnsureI18nMeta to serialize the entire (application,
i18nModelName) workflow, covering ensureI18nRawModel, ensureI18nRawServices,
promoteI18nCanonicalTip, meta.RecomputeEffective, and ACL seeding under one
per-key lock or equivalent. Preserve existing error propagation while preventing
concurrent initialization from creating duplicate canonical rows, and add a
regression test that runs concurrent initialization for the same application and
verifies only one canonical raw model exists.

In `@internal/module/artifact/build/backend/builder_test.go`:
- Around line 712-740: Extend the persistModuleModels test after the existing
RawModel assertions to query meta.Model for module-1 and assert that only the
current partner projections remain, excluding the seeded stale model and nil
entry. Keep the assertion focused on the expected model count and paths/names so
regressions leaving /models/stale in meta_model fail.

---

Nitpick comments:
In `@pkg/meta/extends_expand_coverage_test.go`:
- Around line 39-90: Update TestExpandModelsAlongExtends_CacheHitViaTwoChildren
to verify cache reuse by instrumenting or counting parent loads and asserting
the shared parent is loaded only once while expanding both children. Keep the
existing inheritance assertions, or rename the test to reflect shared-parent
inheritance if load-count instrumentation is not available.

In `@pkg/meta/recompute_coverage_test.go`:
- Around line 525-530: Remove the unreachable else branch in the test cleanup
around tc.drop, and always call db2.Migrator().DropTable(tc.drop). Delete the
obsolete drop-ordering comment while preserving the existing cleanup behavior.
🪄 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: 91fecf79-5729-4e7e-a482-e33acabe44fa

📥 Commits

Reviewing files that changed from the base of the PR and between 192fe2d and 19eb6ad.

📒 Files selected for processing (23)
  • internal/i18n/models/i18n_meta.go
  • internal/i18n/models/i18n_meta_test.go
  • internal/module/artifact/build/backend/app_setting_coverage_test.go
  • internal/module/artifact/build/backend/builder.go
  • internal/module/artifact/build/backend/builder_test.go
  • internal/module/artifact/build/backend/field_default_coverage_test.go
  • internal/module/artifact/build/web/webBuilder_test.go
  • internal/module/evolution/schema/foreignkey.go
  • internal/module/evolution/schema/foreignkey_test.go
  • internal/module/evolution/schema/migrator.go
  • internal/module/evolution/schema/migrator_test.go
  • internal/module/lifecycle/installer.go
  • internal/module/lifecycle/installer_commit_test.go
  • internal/module/lifecycle/uninstaller_clean_models_test.go
  • internal/module/lifecycle/upgrader.go
  • internal/module/metaeff/recompute_test.go
  • pkg/meta/dual_store_migrate.go
  • pkg/meta/dual_store_migrate_coverage_test.go
  • pkg/meta/extends_expand.go
  • pkg/meta/extends_expand_coverage_test.go
  • pkg/meta/recompute.go
  • pkg/meta/recompute_coverage_test.go
  • pkg/meta/recompute_test.go
💤 Files with no reviewable changes (1)
  • pkg/meta/extends_expand.go
🚧 Files skipped from review as they are similar to previous changes (11)
  • internal/module/lifecycle/upgrader.go
  • internal/module/metaeff/recompute_test.go
  • internal/module/lifecycle/installer.go
  • pkg/meta/recompute_test.go
  • pkg/meta/dual_store_migrate_coverage_test.go
  • internal/module/artifact/build/backend/app_setting_coverage_test.go
  • pkg/meta/dual_store_migrate.go
  • internal/module/lifecycle/uninstaller_clean_models_test.go
  • pkg/meta/recompute.go
  • internal/module/artifact/build/backend/field_default_coverage_test.go
  • internal/module/artifact/build/backend/builder.go

Comment thread internal/module/artifact/build/backend/builder_test.go
Comment thread internal/module/artifact/build/web/webBuilder_test.go
Comment thread pkg/meta/recompute_coverage_test.go
Comment thread pkg/meta/recompute_coverage_test.go
- Prefer same-application raw parents when expanding Extends along path.

- Run RecomputeEffective delete+persist in one transaction to avoid catalog holes.

- Harden related builder/web/recompute tests for preference and rollback paths.

Co-authored-by: Cursor <cursoragent@cursor.com>
@buke

buke commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

@cursor review

- Exercise cross-application Extends parent fallback and load errors.

- Promote tip via sibling CreatedAt and skip declaration shells after effective.

Co-authored-by: Cursor <cursoragent@cursor.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit eb9df43. Configure here.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 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 `@pkg/meta/extends_expand_coverage_test.go`:
- Around line 144-163: Update the test fixtures in the parent creation loop for
foreign and home to assign explicit, distinct creation timestamps, making
foreign newer than home. Preserve the existing application, path, and insertion
setup while ensuring the timestamp fields used by recency ordering are populated
deterministically.
- Around line 178-195: Update expandShapeAlongExtends caching to distinguish
parent shapes by both application and model path, or by the resolved parent
identity, so same-path declarations from different applications cannot collide.
Extend the coverage in ExpandModelsAlongExtends to expand home and foreign
children together, asserting each receives only its own application’s parent
fields while retaining its child fields and preserving same-path local
declarations in a batch.
🪄 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: 185996bb-2104-495e-89d2-ecfa6a77c923

📥 Commits

Reviewing files that changed from the base of the PR and between 19eb6ad and 3130e6a.

📒 Files selected for processing (8)
  • internal/i18n/models/i18n_meta.go
  • internal/i18n/models/i18n_meta_test.go
  • internal/module/artifact/build/backend/builder_test.go
  • internal/module/artifact/build/web/webBuilder_test.go
  • pkg/meta/extends_expand.go
  • pkg/meta/extends_expand_coverage_test.go
  • pkg/meta/recompute.go
  • pkg/meta/recompute_coverage_test.go
🚧 Files skipped from review as they are similar to previous changes (7)
  • pkg/meta/extends_expand.go
  • internal/i18n/models/i18n_meta.go
  • internal/i18n/models/i18n_meta_test.go
  • internal/module/artifact/build/backend/builder_test.go
  • internal/module/artifact/build/web/webBuilder_test.go
  • pkg/meta/recompute_coverage_test.go
  • pkg/meta/recompute.go

Comment thread pkg/meta/extends_expand_coverage_test.go
Comment thread pkg/meta/extends_expand_coverage_test.go
- Add declaration helpers so i18n, schema, AppSetting, FieldDefault, and uninstall stop using Raw* CRUD directly.

- Keep effective recompute and uninstall cleanup on the facade path, and rename auth rawModel to modelFullName.

- Resolve e2e Playwright from workspace node_modules so a PATH Homebrew install cannot skew the test registry.

Co-authored-by: Cursor <cursoragent@cursor.com>

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (4)
pkg/meta/declaration_test.go (1)

149-155: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert that the child rows are deleted.

The test is named _Cascade but only counts RawModel rows. Orphaned RawField and RawService rows would still pass. Add counts for the child tables so a regression in the cascade order is caught.

♻️ Proposed additional assertions
 	var n int64
 	if err := db.Unscoped().Model(&RawModel{}).Where("id = ?", id).Count(&n).Error; err != nil || n != 0 {
 		t.Fatalf("left=%d err=%v", n, err)
 	}
+	for name, dest := range map[string]any{"field": &RawField{}, "service": &RawService{}} {
+		var c int64
+		if err := db.Unscoped().Model(dest).Where("model_id = ?", id).Count(&c).Error; err != nil || c != 0 {
+			t.Fatalf("%s rows left=%d err=%v", name, c, err)
+		}
+	}
🤖 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 `@pkg/meta/declaration_test.go` around lines 149 - 155, Extend the cascade
assertions in the DeleteDeclarationTrees test to count RawField and RawService
rows associated with the deleted declaration, in addition to RawModel. Fail the
test when either child-table count is nonzero, while preserving the existing
error reporting and parent-row assertion.
internal/module/lifecycle/uninstaller_model_data_test.go (1)

174-180: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the identity of the surviving effective model.

The test counts one effective (partner, Partner) row. A stale effective row that recomputation failed to replace also satisfies that count. Load the row and assert its Path equals basePath and its ModuleId equals baseMod.Id. The assertion then proves recomputation reprojected from the surviving base declaration.

♻️ Proposed assertion
-	var effectiveCount int64
-	if err := db.Model(&meta.Model{}).Where("application = ? AND name = ?", "partner", "Partner").Count(&effectiveCount).Error; err != nil {
-		t.Fatalf("count effective model: %v", err)
-	}
-	if effectiveCount != 1 {
-		t.Fatalf("survivor raw should yield one effective model, count = %d", effectiveCount)
-	}
+	var effectives []meta.Model
+	if err := db.Where("application = ? AND name = ?", "partner", "Partner").Find(&effectives).Error; err != nil {
+		t.Fatalf("load effective models: %v", err)
+	}
+	if len(effectives) != 1 {
+		t.Fatalf("survivor raw should yield one effective model, count = %d", len(effectives))
+	}
+	if effectives[0].Path != basePath || effectives[0].ModuleId.String != baseMod.Id.String {
+		t.Fatalf("effective model = {path:%q module:%q}, want {path:%q module:%q}",
+			effectives[0].Path, effectives[0].ModuleId.String, basePath, baseMod.Id.String)
+	}
🤖 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 `@internal/module/lifecycle/uninstaller_model_data_test.go` around lines 174 -
180, Extend the effective-model verification after the count in the relevant
test to load the surviving meta.Model row for application “partner” and name
“Partner”, then assert its Path equals basePath and ModuleId equals baseMod.Id.
Preserve the existing count assertion and use the loaded row to prove
recomputation reprojected the surviving base declaration.
pkg/meta/declaration.go (2)

86-130: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick win

The declaration facade mutates the raw catalog without transactions. Both write paths in pkg/meta/declaration.go issue several dependent statements through the bare *gorm.DB handle. A failure partway through leaves the raw catalog in a state that no single statement produced: a declaration with a new module_id and a partial service set, or child rows deleted while their parent models survive. The shared remediation is to run each sequence inside db.Transaction and pass the transaction handle to every statement.

  • pkg/meta/declaration.go#L86-L130: wrap the lookup, the module_id update, and the per-service creates in db.Transaction, and replace the db references in the body with the transaction handle. This also closes the window where two concurrent callers both miss the service existence check and create duplicate rows.
  • pkg/meta/declaration.go#L203-L253: base the fresh() closure on the transaction handle instead of root, so the six-step cascade over arguments, decorators, type parameters, parameters, services, fields, and models commits or rolls back as one unit.
🤖 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 `@pkg/meta/declaration.go` around lines 86 - 130, Wrap the declaration update
flow around PersistModelTreeAsRaw in db.Transaction, using the transaction
handle for the lookup, module_id update, and per-service creation. Also update
the fresh() cascade at pkg/meta/declaration.go lines 203-253 to use the
transaction handle instead of root, ensuring all dependent deletes and writes
commit or roll back together; both sites are in the same file.

256-263: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Derive the raw table names from the models.

HasEffectiveCatalog uses (&Model{}).TableName(), but HasDeclarationCatalog hardcodes "meta_raw_model" and "meta_raw_service". If a raw model's TableName() changes, this check returns false and callers such as EnsureI18nMeta skip registration silently and return nil. Use the same derivation in both helpers.

♻️ Proposed change
 	m := db.Migrator()
-	return m.HasTable("meta_raw_model") && m.HasTable("meta_raw_service")
+	return m.HasTable(&RawModel{}) && m.HasTable(&RawService{})
🤖 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 `@pkg/meta/declaration.go` around lines 256 - 263, Update HasDeclarationCatalog
to derive both table names from the corresponding model TableName methods,
matching HasEffectiveCatalog, instead of hardcoding string literals. Preserve
the existing nil-database handling and require both derived tables to exist.
🤖 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 `@pkg/meta/declaration.go`:
- Around line 49-61: Add id ASC ordering callbacks to every unordered
association preload in the PreloadTree block of the declaration query, including
Services, Services.Parameters, Services.TypeParameters, Services.Decorators,
Services.Decorators.Arguments, Decorators, and Decorators.Arguments. Preserve
the existing ordering callbacks for Fields and its nested associations.

---

Nitpick comments:
In `@internal/module/lifecycle/uninstaller_model_data_test.go`:
- Around line 174-180: Extend the effective-model verification after the count
in the relevant test to load the surviving meta.Model row for application
“partner” and name “Partner”, then assert its Path equals basePath and ModuleId
equals baseMod.Id. Preserve the existing count assertion and use the loaded row
to prove recomputation reprojected the surviving base declaration.

In `@pkg/meta/declaration_test.go`:
- Around line 149-155: Extend the cascade assertions in the
DeleteDeclarationTrees test to count RawField and RawService rows associated
with the deleted declaration, in addition to RawModel. Fail the test when either
child-table count is nonzero, while preserving the existing error reporting and
parent-row assertion.

In `@pkg/meta/declaration.go`:
- Around line 86-130: Wrap the declaration update flow around
PersistModelTreeAsRaw in db.Transaction, using the transaction handle for the
lookup, module_id update, and per-service creation. Also update the fresh()
cascade at pkg/meta/declaration.go lines 203-253 to use the transaction handle
instead of root, ensuring all dependent deletes and writes commit or roll back
together; both sites are in the same file.
- Around line 256-263: Update HasDeclarationCatalog to derive both table names
from the corresponding model TableName methods, matching HasEffectiveCatalog,
instead of hardcoding string literals. Preserve the existing nil-database
handling and require both derived tables to exist.
🪄 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: 20a3fcbd-816f-4889-8271-ac1317e9eb01

📥 Commits

Reviewing files that changed from the base of the PR and between 3130e6a and adbe294.

📒 Files selected for processing (30)
  • internal/i18n/models/i18n_meta.go
  • internal/i18n/models/i18n_meta_test.go
  • internal/module/artifact/build/backend/app_setting.go
  • internal/module/artifact/build/backend/app_setting_coverage_test.go
  • internal/module/artifact/build/backend/app_setting_test.go
  • internal/module/artifact/build/backend/builder.go
  • internal/module/artifact/build/backend/builder_test.go
  • internal/module/artifact/build/backend/field_default.go
  • internal/module/artifact/build/backend/field_default_coverage_test.go
  • internal/module/artifact/build/backend/field_default_test.go
  • internal/module/evolution/schema/foreignkey.go
  • internal/module/evolution/schema/foreignkey_test.go
  • internal/module/evolution/schema/helpers_test.go
  • internal/module/evolution/schema/migrator.go
  • internal/module/evolution/schema/migrator_test.go
  • internal/module/lifecycle/installer_commit_test.go
  • internal/module/lifecycle/uninstaller.go
  • internal/module/lifecycle/uninstaller_clean_models_test.go
  • internal/module/lifecycle/uninstaller_model_data_test.go
  • internal/module/metaeff/recompute_test.go
  • internal/testing/e2e/runner.go
  • internal/testing/e2e/runner_test.go
  • modules/auth/service/models/_user_field_rule_eval.ts
  • modules/auth/service/models/user.ts
  • modules/auth/service/tests/authz_context_memoization.test.ts
  • modules/auth/service/tests/field_rule.test.ts
  • pkg/meta/declaration.go
  • pkg/meta/declaration_test.go
  • pkg/meta/recompute.go
  • pkg/meta/recompute_coverage_test.go
🚧 Files skipped from review as they are similar to previous changes (16)
  • internal/module/evolution/schema/foreignkey_test.go
  • internal/module/evolution/schema/migrator.go
  • internal/module/lifecycle/uninstaller.go
  • internal/module/artifact/build/backend/builder.go
  • pkg/meta/recompute_coverage_test.go
  • internal/module/lifecycle/installer_commit_test.go
  • internal/module/artifact/build/backend/builder_test.go
  • internal/module/metaeff/recompute_test.go
  • internal/module/artifact/build/backend/app_setting_coverage_test.go
  • internal/module/artifact/build/backend/field_default_test.go
  • pkg/meta/recompute.go
  • internal/module/artifact/build/backend/app_setting.go
  • internal/module/evolution/schema/foreignkey.go
  • internal/i18n/models/i18n_meta_test.go
  • internal/module/evolution/schema/migrator_test.go
  • internal/module/artifact/build/backend/field_default_coverage_test.go

Comment thread pkg/meta/declaration.go
buke and others added 2 commits August 5, 2026 14:23
- Align RunModule hook fixtures with workspace-first playwright resolution so PATH-only stubs no longer fail preflight.

Co-authored-by: Cursor <cursoragent@cursor.com>
- Key expand-shape cache/local lookups by application+path so same-path parents from different apps cannot collide, and cover the batch case.

- Order declaration PreloadTree associations by id, wrap upsert/delete cascades in transactions, and derive HasDeclarationCatalog table names from models.

- Strengthen cascade and uninstall recompute assertions while keeping effective ModuleId omitted by design.

Co-authored-by: Cursor <cursoragent@cursor.com>
@buke

buke commented Aug 5, 2026

Copy link
Copy Markdown
Contributor Author

@cursor review

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

✅ Bugbot reviewed your changes and found no new issues!

Comment @cursor review or bugbot run to trigger another review on this PR

Reviewed by Cursor Bugbot for commit 6a04215. Configure here.

@buke
buke merged commit 8f5e582 into main Aug 5, 2026
46 checks passed
@buke
buke deleted the feat/meta-effective-dual-store-eds2 branch August 5, 2026 08:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant