You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@fory.apache.org by ch...@apache.org on 2026/07/28 03:07:08 UTC

(fory) branch main updated: feat(dart): add dart inheriance support (#3892)

This is an automated email from the ASF dual-hosted git repository.

chaokunyang pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/fory.git


The following commit(s) were added to refs/heads/main by this push:
     new c91f7c562 feat(dart): add dart inheriance support (#3892)
c91f7c562 is described below

commit c91f7c562b6f4b92cec73df60b04f3905f48ae37
Author: Shawn Yang <sh...@gmail.com>
AuthorDate: Tue Jul 28 11:07:03 2026 +0800

    feat(dart): add dart inheriance support (#3892)
    
    ## Why?
    
    
    
    ## What does this PR do?
    
    
    
    ## Related issues
    
    
    
    ## AI Contribution Checklist
    
    
    
    - [ ] Substantial AI assistance was used in this PR: `yes` / `no`
    - [ ] If `yes`, I included a completed [AI Contribution
    Checklist](https://github.com/apache/fory/blob/main/AI_POLICY.md#9-contributor-checklist-for-ai-assisted-prs)
    in this PR description and the required `AI Usage Disclosure`.
    - [ ] If `yes`, my PR description includes the required `ai_review`
    summary and screenshot evidence or equivalent persisted links of the
    final clean AI review results from both fresh reviewers described in
    `AI_POLICY.md`, the Fory-guided reviewer and the independent general
    reviewer, on the current PR diff or current HEAD after the latest code
    changes.
    
    
    
    ## Does this PR introduce any user-facing change?
    
    
    
    - [ ] Does this PR introduce any public API change?
    - [ ] Does this PR introduce any binary protocol compatibility change?
    
    ## Benchmark
---
 .agents/languages/dart.md                          |   37 +
 .github/workflows/ci.yml                           |   39 +-
 README.md                                          |    2 +-
 benchmarks/dart/run.sh                             |    2 +-
 dart/README.md                                     |   13 +-
 .../fory-test/lib/entity/xlang_test_manual.dart    |    2 +-
 .../fory-test/lib/model/inheritance_models.dart    |  235 +++
 dart/packages/fory-test/pubspec.yaml               |    3 +-
 .../test/cross_lang_test/xlang_test_main.dart      |   44 +
 .../fory-test/test/struct_inheritance_test.dart    |  338 ++++
 .../packages/fory-test/tool/inheritance_smoke.dart |   46 +
 dart/packages/fory/CHANGELOG.md                    |   12 +
 dart/packages/fory/README.md                       |   95 +-
 .../fory/lib/src/annotation/fory_field.dart        |   21 +
 .../fory/lib/src/annotation/fory_struct.dart       |   18 +-
 .../lib/src/codegen/fory_constructor_analysis.dart |  874 +++++++++
 .../fory/lib/src/codegen/fory_generator.dart       | 2058 +++++++++++++++++---
 .../fory/lib/src/codegen/generated_support.dart    |    2 -
 dart/packages/fory/lib/src/meta/field_info.dart    |   12 +
 .../fory/lib/src/resolver/type_resolver.dart       |   63 +-
 .../fory/lib/src/serializer/scalar_conversion.dart |   35 +-
 .../fory/lib/src/serializer/struct_serializer.dart |   39 +-
 dart/packages/fory/pubspec.yaml                    |    4 +-
 dart/packages/fory/test/codegen_test_support.dart  |  132 ++
 .../fory/test/external_type_generator_test.dart    |   70 +-
 .../fory/test/graph_memory_budget_test.dart        |  163 ++
 .../fory/test/runtime_validation_test.dart         |   30 -
 .../struct_constructor_flow_generator_test.dart    |  544 ++++++
 .../test/struct_inheritance_generator_test.dart    |  625 ++++++
 .../struct_private_companion_generator_test.dart   | 1005 ++++++++++
 dart/packages/fory/test/xlang_protocol_test.dart   |  278 +++
 .../analysis_options.yaml}                         |   22 +-
 .../inheritance-test-models/lib/base_models.dart   |   92 +
 .../inheritance-test-models/lib/middle_models.dart |   52 +
 .../pubspec.yaml                                   |   15 +-
 dart/pubspec.yaml                                  |    3 +-
 docs/guide/dart/code-generation.md                 |   93 +-
 docs/guide/dart/external-types.md                  |   11 +-
 docs/guide/dart/grpc-support.md                    |    4 +-
 docs/guide/dart/index.md                           |   10 +-
 docs/guide/dart/schema-evolution.md                |   23 +
 docs/guide/dart/schema-metadata.md                 |   21 +-
 docs/guide/dart/supported-types.md                 |   18 +-
 docs/guide/dart/troubleshooting.md                 |   56 +-
 docs/guide/dart/type-registration.md               |   12 +
 docs/guide/dart/web-platform-support.md            |   25 +-
 docs/guide/dart/xlang-serialization.md             |   17 +-
 docs/specification/xlang_implementation_guide.md   |  187 +-
 integration_tests/idl_tests/run_dart_tests.sh      |    2 +-
 .../java/org/apache/fory/xlang/DartXlangTest.java  |   89 +-
 50 files changed, 7121 insertions(+), 472 deletions(-)

diff --git a/.agents/languages/dart.md b/.agents/languages/dart.md
index 814b64aad..47613198b 100644
--- a/.agents/languages/dart.md
+++ b/.agents/languages/dart.md
@@ -19,6 +19,43 @@ Load this file when changing `dart/`.
   existing resolver, field, carrier, reference, and compatible-read paths. Do
   not add runtime declaration instances, callbacks, member lookup, target-copy
   objects, a second registration API, or a second carrier/root flow.
+- Ordinary `ForyStruct` generation must discover every real instance storage
+  field on the concrete superclass and applied-mixin chain before considering
+  Dart accessibility. Enumerate each instantiated hierarchy layer's declared
+  fields; do not use only the child `element.fields`, `allSupertypes`, member
+  lookup, import visibility, or privacy as field discovery.
+- Process each discovered ordinary field through one owner path: apply only
+  its declaration-owned `ForyField(ignore: true)`, substitute the concrete
+  generic type, resolve direct or companion access, prove construction, then
+  build the concrete child's one globally sorted flattened schema. Never omit
+  a non-ignored field because it is private, inaccessible, final, hidden, or
+  unsupported; report a generation error.
+- Public inherited fields and private inherited fields declared in the child's
+  library use direct generated access without a parent annotation.
+  Cross-library private storage requires
+  `ForyStruct(exposePrivateFields: true)` on a public hierarchy boundary in
+  that field's declaring library. That provider emits only public typed static
+  getter/setter access for its own library's private state; it owns no schema,
+  serializer, construction, registration, reference, or graph-memory state.
+  Each declaring library in a multi-library hierarchy must provide its own
+  boundary. A consumer annotation cannot authorize another library's field.
+- A non-ignored ordinary `final` or `late final` field must receive its decoded
+  value unchanged from the selected concrete-child constructor through
+  element-proven field formals, super formals, redirects, or explicit
+  initializer/super arguments. Names are not bindings. Do not post-set final
+  state, use reflection, or fall back when identity flow cannot be proven.
+- The concrete ordinary child owns one flattened schema, serializer,
+  constructor reconstruction, reference publication path, and graph-memory
+  object charge. Inherited reference metadata enters the existing recursive
+  reference analysis once. Do not add reference state, change `ReadContext` or
+  serializer reference-call contracts, or invoke a parent serializer for
+  inheritance.
+- Keep external target fields explicit. Ordinary hierarchy discovery and
+  `exposePrivateFields` must never scan or expose an external target hierarchy.
+- Across packages, generate and publish the provider library's `.fory.dart`
+  part before building a consumer that uses its private-field access companion.
+  A direct import or re-export must expose both the public boundary and
+  companion.
 - Constructor-based external structural serializers must reject every
   statically provable reference-tracked path back to the target, including
   paths through supported list, set, and map type arguments. Do not simulate
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 52bee60ef..450bd1a73 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -1742,11 +1742,15 @@ jobs:
         run: |
           (
             cd dart/packages/fory
-            dart run build_runner build --delete-conflicting-outputs
+            dart run build_runner build
+          )
+          (
+            cd dart/packages/inheritance-test-models
+            dart run build_runner build
           )
           (
             cd dart/packages/fory-test
-            dart run build_runner build --delete-conflicting-outputs
+            dart run build_runner build
           )
       - name: Run Dart Xlang Test
         env:
@@ -1781,16 +1785,37 @@ jobs:
         run: |
           (
             cd dart/packages/fory
-            dart run build_runner build --delete-conflicting-outputs
+            dart run build_runner build
+          )
+          (
+            cd dart/packages/inheritance-test-models
+            dart run build_runner build
+          )
+          (
+            cd dart/packages/fory-test
+            dart run build_runner build
+          )
+      - name: Run VM tests
+        run: |
+          (
+            cd dart/packages/fory
+            dart test
           )
           (
             cd dart/packages/fory-test
-            dart run build_runner build --delete-conflicting-outputs
+            dart test
           )
-      - name: Run tests
+      - name: Run inheritance AOT and JavaScript smoke
         run: |
           cd dart/packages/fory-test
-          dart test
+          dart compile exe \
+            -o "$RUNNER_TEMP/fory_inheritance_smoke" \
+            tool/inheritance_smoke.dart
+          "$RUNNER_TEMP/fory_inheritance_smoke"
+          dart compile js --fatal-warnings --no-source-maps -O2 \
+            -o "$RUNNER_TEMP/fory_inheritance_smoke.js" \
+            tool/inheritance_smoke.dart
+          node "$RUNNER_TEMP/fory_inheritance_smoke.js"
       - name: Run web tests
         run: |
           cd dart/packages/fory
@@ -1910,7 +1935,7 @@ jobs:
       - name: Generate Flutter smoke code
         run: |
           cd "$FLUTTER_SMOKE_DIR"
-          dart run build_runner build --delete-conflicting-outputs
+          dart run build_runner build
       - name: Run Flutter smoke tests
         run: |
           cd "$FLUTTER_SMOKE_DIR"
diff --git a/README.md b/README.md
index e358a480c..e124a3463 100644
--- a/README.md
+++ b/README.md
@@ -561,7 +561,7 @@ Dart uses the xlang wire format directly. Generate the companion file before
 running:
 
 ```bash
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
 
 **Swift**
diff --git a/benchmarks/dart/run.sh b/benchmarks/dart/run.sh
index 5813adac2..a1869a7f2 100644
--- a/benchmarks/dart/run.sh
+++ b/benchmarks/dart/run.sh
@@ -107,7 +107,7 @@ echo "Preparing Dart benchmark package..."
 echo "============================================"
 dart pub get
 ./tool/generate_proto.sh
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 
 echo ""
 echo "============================================"
diff --git a/dart/README.md b/dart/README.md
index 54dc174c6..531df21b5 100644
--- a/dart/README.md
+++ b/dart/README.md
@@ -18,6 +18,7 @@ including getting started, API reference, and code examples.
 | `packages/fory/example/`              | Annotated example with generated output             |
 | `packages/fory/test/`                 | Unit and integration tests                          |
 | `packages/external-type-test-models/` | Fory-free dependency models for external-type tests |
+| `packages/inheritance-test-models/`   | Cross-library inheritance test providers            |
 | `test/`                               | Cross-language integration tests                    |
 
 ## Type Mapping
@@ -81,7 +82,7 @@ class Person {
 ```
 
 ```bash
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
 
 Serialize and deserialize:
@@ -94,6 +95,14 @@ final bytes = fory.serialize(Person()..name = 'Ada'..age = 36);
 final roundTrip = fory.deserialize<Person>(bytes);
 ```
 
+Ordinary annotated classes serialize one flattened view of their concrete
+superclass and applied-mixin storage. Public and same-library private inherited
+fields need no parent annotation. A declaring library that intentionally
+exposes private state to generated serializers in other libraries marks a
+public hierarchy boundary with `@ForyStruct(exposePrivateFields: true)`.
+`@ForyField(ignore: true)` on the field declaration is the only way to omit
+ordinary storage.
+
 ## Development
 
 Run tests from the workspace root:
@@ -107,5 +116,5 @@ Run the code generator on the example:
 
 ```bash
 cd packages/fory
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
diff --git a/dart/packages/fory-test/lib/entity/xlang_test_manual.dart b/dart/packages/fory-test/lib/entity/xlang_test_manual.dart
index 05ea5f6e8..7f4e4c66b 100644
--- a/dart/packages/fory-test/lib/entity/xlang_test_manual.dart
+++ b/dart/packages/fory-test/lib/entity/xlang_test_manual.dart
@@ -103,7 +103,7 @@ const GeneratedFieldType _refOverrideElementFieldType = GeneratedFieldType(
 const List<GeneratedFieldInfo>
 _refOverrideContainerForyFieldInfo = <GeneratedFieldInfo>[
   // Keep this manual fixture in the same protocol order as generated structs:
-  // all fields are non-primitive, so field identifiers order the wire payload.
+  // all fields are non-primitive, so wire names order the payload.
   GeneratedFieldInfo(
     name: 'listField',
     identifier: 'list_field',
diff --git a/dart/packages/fory-test/lib/model/inheritance_models.dart b/dart/packages/fory-test/lib/model/inheritance_models.dart
new file mode 100644
index 000000000..716dc8b71
--- /dev/null
+++ b/dart/packages/fory-test/lib/model/inheritance_models.dart
@@ -0,0 +1,235 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+library;
+
+import 'package:fory/fory.dart';
+import 'package:inheritance_test_models/base_models.dart' as base;
+import 'package:inheritance_test_models/middle_models.dart' as middle;
+
+part 'inheritance_models.fory.dart';
+
+@ForyStruct()
+final class CrossLibraryChild extends middle.PublicMiddleBoundary {
+  // Keep explicit forwarding to exercise constructor identity analysis.
+  // ignore: use_super_parameters
+  CrossLibraryChild(String rootValue, int middleValue, this.childFinal)
+    : super(rootValue, middleValue);
+
+  @ForyField(id: 21)
+  final bool childFinal;
+
+  @ForyField(id: 22)
+  String childMutable = '';
+}
+
+class OptionalMutableBase {
+  OptionalMutableBase([int decoded = 0])
+    : inheritedMutable = decoded,
+      observedAtConstruction = decoded;
+
+  @ForyField(id: 1)
+  int inheritedMutable;
+
+  @ForyField(ignore: true)
+  final int observedAtConstruction;
+}
+
+@ForyStruct()
+final class OptionalMutableChild extends OptionalMutableBase {
+  OptionalMutableChild(this.fixed, [int decoded = 0]) : super(decoded);
+
+  @ForyField(id: 2)
+  final int fixed;
+}
+
+class AllWritableOptionalBase {
+  AllWritableOptionalBase([int decoded = 0])
+    : inheritedMutable = decoded,
+      observedAtConstruction = decoded;
+
+  @ForyField(id: 1)
+  int inheritedMutable;
+
+  @ForyField(ignore: true)
+  final int observedAtConstruction;
+}
+
+@ForyStruct()
+final class AllWritableOptionalChild extends AllWritableOptionalBase {
+  AllWritableOptionalChild([super.decoded]);
+}
+
+@ForyStruct()
+final class XlangInheritedChild extends base.XlangInheritanceBoundary {
+  XlangInheritedChild();
+
+  @ForyField(id: 1, type: Int32Type())
+  int childInt = 0;
+
+  @ForyField(id: 5)
+  bool childFlag = false;
+
+  @ForyField(id: 9)
+  String childText = '';
+}
+
+class PublicXlangInheritanceParent {
+  @ForyField(id: 2, type: Int32Type())
+  int parentInt = 0;
+
+  @ForyField(id: 6)
+  bool parentFlag = false;
+
+  @ForyField(id: 10)
+  String parentText = '';
+}
+
+@ForyStruct()
+final class PublicXlangInheritedChild extends PublicXlangInheritanceParent {
+  PublicXlangInheritedChild();
+
+  @ForyField(id: 1, type: Int32Type())
+  int childInt = 0;
+
+  @ForyField(id: 5)
+  bool childFlag = false;
+
+  @ForyField(id: 9)
+  String childText = '';
+}
+
+@ForyStruct()
+final class FlatXlangInheritedStruct {
+  FlatXlangInheritedStruct();
+
+  @ForyField(id: 1, type: Int32Type())
+  int childInt = 0;
+
+  @ForyField(id: 2, type: Int32Type())
+  int parentInt = 0;
+
+  @ForyField(id: 5)
+  bool childFlag = false;
+
+  @ForyField(id: 6)
+  bool parentFlag = false;
+
+  @ForyField(id: 9)
+  String childText = '';
+
+  @ForyField(id: 10)
+  String parentText = '';
+}
+
+@ForyStruct()
+class CompatibleOldParent {
+  CompatibleOldParent();
+
+  @ForyField(id: 1)
+  String inheritedStable = '';
+}
+
+@ForyStruct()
+final class CompatibleOldChild extends CompatibleOldParent {
+  CompatibleOldChild();
+
+  @ForyField(id: 3)
+  String childStable = '';
+}
+
+@ForyStruct()
+class CompatibleNewParent {
+  CompatibleNewParent();
+
+  @ForyField(id: 1)
+  String inheritedStable = '';
+
+  @ForyField(id: 2)
+  String inheritedAdded = 'new-default';
+}
+
+@ForyStruct()
+final class CompatibleNewChild extends CompatibleNewParent {
+  CompatibleNewChild();
+
+  @ForyField(id: 3)
+  String childStable = '';
+}
+
+@ForyStruct()
+final class ReferenceLeaf {
+  ReferenceLeaf();
+
+  @ForyField(id: 1)
+  String label = '';
+}
+
+class ReferenceFieldsBase<T> {
+  ReferenceFieldsBase();
+
+  @ForyField(id: 1, ref: true)
+  T? first;
+
+  @ForyField(id: 2, ref: true)
+  T? second;
+
+  @ListField(id: 3, element: DeclaredType(ref: true))
+  List<T> nested = <T>[];
+}
+
+@ForyStruct()
+final class InheritedReferenceModel extends ReferenceFieldsBase<ReferenceLeaf> {
+  InheritedReferenceModel();
+}
+
+@ForyStruct()
+final class FlatReferenceModel {
+  FlatReferenceModel();
+
+  @ForyField(id: 1, ref: true)
+  ReferenceLeaf? first;
+
+  @ForyField(id: 2, ref: true)
+  ReferenceLeaf? second;
+
+  @ListField(id: 3, element: DeclaredType(ref: true))
+  List<ReferenceLeaf> nested = <ReferenceLeaf>[];
+}
+
+class SelfReferenceBase<T> {
+  SelfReferenceBase();
+
+  @ForyField(id: 1, ref: true)
+  T? self;
+}
+
+@ForyStruct()
+final class InheritedSelfReference
+    extends SelfReferenceBase<InheritedSelfReference> {
+  InheritedSelfReference();
+}
+
+@ForyStruct()
+final class FlatSelfReference {
+  FlatSelfReference();
+
+  @ForyField(id: 1, ref: true)
+  FlatSelfReference? self;
+}
diff --git a/dart/packages/fory-test/pubspec.yaml b/dart/packages/fory-test/pubspec.yaml
index 204c72226..88fca2e3f 100644
--- a/dart/packages/fory-test/pubspec.yaml
+++ b/dart/packages/fory-test/pubspec.yaml
@@ -27,9 +27,10 @@ environment:
 dependencies:
   external_type_test_models: 1.0.0
   fory: 1.5.0-dev
+  inheritance_test_models: 1.0.0
 
 dev_dependencies:
-  build_runner: '>=2.7.0 <3.0.0'
+  build_runner: ">=2.7.0 <3.0.0"
   lints: ^6.1.0
   test: ^1.31.1
   vm_service: ^15.0.0
diff --git a/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart b/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
index abe80edbf..2f6d4956e 100644
--- a/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
+++ b/dart/packages/fory-test/test/cross_lang_test/xlang_test_main.dart
@@ -26,6 +26,7 @@ import 'package:fory/fory.dart';
 import 'package:fory/src/util/hash_util.dart';
 import 'package:fory_test/entity/xlang_test_models.dart';
 import 'package:fory_test/model/external_serializers.dart';
+import 'package:fory_test/model/inheritance_models.dart';
 
 String _dataFilePath() {
   final path = Platform.environment['DATA_FILE'];
@@ -322,6 +323,43 @@ void _runExternalUser({int? id, String? name}) {
   _writeFile(fory.serialize(user));
 }
 
+void _registerInheritedXlangChild(Fory fory) {
+  InheritanceModelsForyModule.register(fory, XlangInheritedChild, id: 1101);
+}
+
+XlangInheritedChild _newInheritedXlangChild() {
+  return XlangInheritedChild()
+    ..childInt = 101
+    ..childFlag = true
+    ..childText = 'child'
+    ..setParentValues(parentInt: 202, parentFlag: false, parentText: 'parent');
+}
+
+void _verifyInheritedXlangChild(XlangInheritedChild value) {
+  if (value.childInt != 101 ||
+      value.parentInt != 202 ||
+      !value.childFlag ||
+      value.parentFlag ||
+      value.childText != 'child' ||
+      value.parentText != 'parent') {
+    throw StateError('Unexpected inherited xlang field values.');
+  }
+}
+
+void _roundTripInheritedXlangChild() {
+  final fory = _newFory();
+  _registerInheritedXlangChild(fory);
+  final value = fory.deserialize<XlangInheritedChild>(_readFile());
+  _verifyInheritedXlangChild(value);
+  _writeFile(fory.serialize(value));
+}
+
+void _writeInheritedXlangChild() {
+  final fory = _newFory();
+  _registerInheritedXlangChild(fory);
+  _writeFile(fory.serialize(_newInheritedXlangChild()));
+}
+
 void _runCollectionElementRefOverride() {
   final fory = _newFory();
   registerXlangType(fory, RefOverrideElement, id: 701);
@@ -469,6 +507,12 @@ void _runCase(String caseName) {
     case 'test_external_struct_name':
       _runExternalUser(name: 'test.external_user');
       return;
+    case 'test_inherited_flat_from_java':
+      _roundTripInheritedXlangChild();
+      return;
+    case 'test_inherited_flat_from_dart':
+      _writeInheritedXlangChild();
+      return;
     case 'test_list':
     case 'test_map':
     case 'test_item':
diff --git a/dart/packages/fory-test/test/struct_inheritance_test.dart b/dart/packages/fory-test/test/struct_inheritance_test.dart
new file mode 100644
index 000000000..e210dc17f
--- /dev/null
+++ b/dart/packages/fory-test/test/struct_inheritance_test.dart
@@ -0,0 +1,338 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import 'package:fory/fory.dart';
+import 'package:fory_test/model/inheritance_models.dart';
+import 'package:test/test.dart';
+
+void _registerChild(Fory fory, {required bool byName}) {
+  InheritanceModelsForyModule.register(
+    fory,
+    CrossLibraryChild,
+    id: byName ? null : 401,
+    name: byName ? 'inheritance.CrossLibraryChild' : null,
+  );
+}
+
+void _registerReferenceLeaf(Fory fory) {
+  InheritanceModelsForyModule.register(fory, ReferenceLeaf, id: 402);
+}
+
+void _registerReferenceRoot(Fory fory, Type type) {
+  InheritanceModelsForyModule.register(
+    fory,
+    type,
+    name: 'inheritance.ReferenceRoot',
+  );
+}
+
+void _registerSelfRoot(Fory fory, Type type) {
+  InheritanceModelsForyModule.register(
+    fory,
+    type,
+    name: 'inheritance.SelfRoot',
+  );
+}
+
+void _registerCompatibleHierarchy(
+  Fory fory, {
+  required Type parentType,
+  required Type childType,
+}) {
+  InheritanceModelsForyModule.register(
+    fory,
+    parentType,
+    name: 'inheritance.CompatibleParent',
+  );
+  InheritanceModelsForyModule.register(
+    fory,
+    childType,
+    name: 'inheritance.CompatibleChild',
+  );
+}
+
+void main() {
+  group('ordinary struct inheritance', () {
+    for (final compatible in <bool>[false, true]) {
+      test('round trips private hierarchy compatible=$compatible', () {
+        final fory = Fory(compatible: compatible);
+        _registerChild(fory, byName: compatible);
+
+        final input =
+            CrossLibraryChild('base-final', 37, true)
+              ..setBasePrivateName('base-private')
+              ..setMiddlePrivateName('middle-private')
+              ..setMixinCount(9)
+              ..publicLabel = 'base-public'
+              ..publicMiddleCount = 12
+              ..childMutable = 'child';
+
+        final result = fory.deserialize<CrossLibraryChild>(
+          fory.serialize(input),
+        );
+
+        expect(result.ownerValue, 'base-final');
+        expect(result.middleValue, 37);
+        expect(result.childFinal, isTrue);
+        expect(result.basePrivateName, 'base-private');
+        expect(result.middlePrivateName, 'middle-private');
+        expect(result.mixinCount, 9);
+        expect(result.publicLabel, 'base-public');
+        expect(result.publicMiddleCount, 12);
+        expect(result.childMutable, 'child');
+      });
+    }
+
+    test('passes optional mutable state through construction', () {
+      final fory = Fory();
+      InheritanceModelsForyModule.register(fory, OptionalMutableChild, id: 405);
+
+      final result = fory.deserialize<OptionalMutableChild>(
+        fory.serialize(OptionalMutableChild(7, 19)),
+      );
+
+      expect(result.fixed, 7);
+      expect(result.inheritedMutable, 19);
+      expect(result.observedAtConstruction, 19);
+    });
+
+    test('analyzes optional flow for all-writable state', () {
+      final fory = Fory();
+      InheritanceModelsForyModule.register(
+        fory,
+        AllWritableOptionalChild,
+        id: 406,
+      );
+
+      final result = fory.deserialize<AllWritableOptionalChild>(
+        fory.serialize(AllWritableOptionalChild(23)),
+      );
+
+      expect(result.inheritedMutable, 23);
+      expect(result.observedAtConstruction, 23);
+    });
+
+    for (final compatible in <bool>[false, true]) {
+      test('matches flat and inherited schemas compatible=$compatible', () {
+        final privateFory = Fory(compatible: compatible);
+        final publicFory = Fory(compatible: compatible);
+        final flatFory = Fory(compatible: compatible);
+        InheritanceModelsForyModule.register(
+          privateFory,
+          XlangInheritedChild,
+          id: 404,
+        );
+        InheritanceModelsForyModule.register(
+          publicFory,
+          PublicXlangInheritedChild,
+          id: 404,
+        );
+        InheritanceModelsForyModule.register(
+          flatFory,
+          FlatXlangInheritedStruct,
+          id: 404,
+        );
+
+        final privateValue =
+            XlangInheritedChild()
+              ..childInt = 11
+              ..childFlag = true
+              ..childText = 'child'
+              ..setParentValues(
+                parentInt: 22,
+                parentFlag: false,
+                parentText: 'parent',
+              );
+        final publicValue =
+            PublicXlangInheritedChild()
+              ..childInt = 11
+              ..parentInt = 22
+              ..childFlag = true
+              ..parentFlag = false
+              ..childText = 'child'
+              ..parentText = 'parent';
+        final flatValue =
+            FlatXlangInheritedStruct()
+              ..childInt = 11
+              ..parentInt = 22
+              ..childFlag = true
+              ..parentFlag = false
+              ..childText = 'child'
+              ..parentText = 'parent';
+
+        final privateBytes = privateFory.serialize(privateValue);
+        final publicBytes = publicFory.serialize(publicValue);
+        final flatBytes = flatFory.serialize(flatValue);
+        expect(privateBytes, publicBytes);
+        expect(privateBytes, flatBytes);
+
+        final privateResult = privateFory.deserialize<XlangInheritedChild>(
+          privateBytes,
+        );
+        final publicResult = publicFory.deserialize<PublicXlangInheritedChild>(
+          publicBytes,
+        );
+        final flatResult = flatFory.deserialize<FlatXlangInheritedStruct>(
+          flatBytes,
+        );
+        expect(privateResult.parentInt, 22);
+        expect(privateResult.parentText, 'parent');
+        expect(publicResult.parentInt, 22);
+        expect(publicResult.parentText, 'parent');
+        expect(flatResult.parentInt, 22);
+        expect(flatResult.parentText, 'parent');
+      });
+    }
+  });
+
+  group('compatible inherited evolution', () {
+    test('reads added and missing inherited fields both directions', () {
+      final oldFory = Fory(compatible: true);
+      final newFory = Fory(compatible: true);
+      _registerCompatibleHierarchy(
+        oldFory,
+        parentType: CompatibleOldParent,
+        childType: CompatibleOldChild,
+      );
+      _registerCompatibleHierarchy(
+        newFory,
+        parentType: CompatibleNewParent,
+        childType: CompatibleNewChild,
+      );
+
+      final oldInput =
+          CompatibleOldChild()
+            ..inheritedStable = 'old-parent'
+            ..childStable = 'old-child';
+      final newResult = newFory.deserialize<CompatibleNewChild>(
+        oldFory.serialize(oldInput),
+      );
+      expect(newResult.inheritedStable, 'old-parent');
+      expect(newResult.inheritedAdded, 'new-default');
+      expect(newResult.childStable, 'old-child');
+
+      final newInput =
+          CompatibleNewChild()
+            ..inheritedStable = 'new-parent'
+            ..inheritedAdded = 'new-only'
+            ..childStable = 'new-child';
+      final oldResult = oldFory.deserialize<CompatibleOldChild>(
+        newFory.serialize(newInput),
+      );
+      expect(oldResult.inheritedStable, 'new-parent');
+      expect(oldResult.childStable, 'new-child');
+    });
+
+    test('registers each parent independently', () {
+      final oldFory = Fory(compatible: true);
+      InheritanceModelsForyModule.register(
+        oldFory,
+        CompatibleOldParent,
+        name: 'inheritance.CompatibleParent',
+      );
+      final oldParent = CompatibleOldParent()..inheritedStable = 'old-parent';
+      final oldResult = oldFory.deserialize<CompatibleOldParent>(
+        oldFory.serialize(oldParent),
+      );
+      expect(oldResult.inheritedStable, 'old-parent');
+
+      final newFory = Fory(compatible: true);
+      InheritanceModelsForyModule.register(
+        newFory,
+        CompatibleNewParent,
+        name: 'inheritance.CompatibleParent',
+      );
+      final newParent =
+          CompatibleNewParent()
+            ..inheritedStable = 'new-parent'
+            ..inheritedAdded = 'new-field';
+      final newResult = newFory.deserialize<CompatibleNewParent>(
+        newFory.serialize(newParent),
+      );
+      expect(newResult.inheritedStable, 'new-parent');
+      expect(newResult.inheritedAdded, 'new-field');
+    });
+  });
+
+  group('equivalent flat references', () {
+    for (final compatible in <bool>[false, true]) {
+      test('matches inherited shared refs compatible=$compatible', () {
+        final inheritedFory = Fory(compatible: compatible);
+        final flatFory = Fory(compatible: compatible);
+        _registerReferenceLeaf(inheritedFory);
+        _registerReferenceLeaf(flatFory);
+        _registerReferenceRoot(inheritedFory, InheritedReferenceModel);
+        _registerReferenceRoot(flatFory, FlatReferenceModel);
+
+        final inheritedLeaf = ReferenceLeaf()..label = 'shared';
+        final flatLeaf = ReferenceLeaf()..label = 'shared';
+        final inherited =
+            InheritedReferenceModel()
+              ..first = inheritedLeaf
+              ..second = inheritedLeaf
+              ..nested = <ReferenceLeaf>[inheritedLeaf, inheritedLeaf];
+        final flat =
+            FlatReferenceModel()
+              ..first = flatLeaf
+              ..second = flatLeaf
+              ..nested = <ReferenceLeaf>[flatLeaf, flatLeaf];
+
+        final inheritedBytes = inheritedFory.serialize(inherited);
+        final flatBytes = flatFory.serialize(flat);
+        expect(inheritedBytes, flatBytes);
+
+        final inheritedResult = inheritedFory
+            .deserialize<InheritedReferenceModel>(inheritedBytes);
+        final flatResult = flatFory.deserialize<FlatReferenceModel>(flatBytes);
+        expect(
+          identical(inheritedResult.first, inheritedResult.second),
+          isTrue,
+        );
+        expect(
+          identical(inheritedResult.first, inheritedResult.nested.first),
+          isTrue,
+        );
+        expect(identical(flatResult.first, flatResult.second), isTrue);
+        expect(identical(flatResult.first, flatResult.nested.first), isTrue);
+      });
+
+      test('matches inherited self refs compatible=$compatible', () {
+        final inheritedFory = Fory(compatible: compatible);
+        final flatFory = Fory(compatible: compatible);
+        _registerSelfRoot(inheritedFory, InheritedSelfReference);
+        _registerSelfRoot(flatFory, FlatSelfReference);
+
+        final inherited = InheritedSelfReference();
+        inherited.self = inherited;
+        final flat = FlatSelfReference();
+        flat.self = flat;
+
+        final inheritedBytes = inheritedFory.serialize(inherited);
+        final flatBytes = flatFory.serialize(flat);
+        expect(inheritedBytes, flatBytes);
+
+        final inheritedResult = inheritedFory
+            .deserialize<InheritedSelfReference>(inheritedBytes);
+        final flatResult = flatFory.deserialize<FlatSelfReference>(flatBytes);
+        expect(identical(inheritedResult, inheritedResult.self), isTrue);
+        expect(identical(flatResult, flatResult.self), isTrue);
+      });
+    }
+  });
+}
diff --git a/dart/packages/fory-test/tool/inheritance_smoke.dart b/dart/packages/fory-test/tool/inheritance_smoke.dart
new file mode 100644
index 000000000..9e85c0459
--- /dev/null
+++ b/dart/packages/fory-test/tool/inheritance_smoke.dart
@@ -0,0 +1,46 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+import 'package:fory/fory.dart';
+import 'package:fory_test/model/inheritance_models.dart';
+
+void main() {
+  final fory = Fory();
+  InheritanceModelsForyModule.register(fory, CrossLibraryChild, id: 401);
+  final input =
+      CrossLibraryChild('base-final', 37, true)
+        ..setBasePrivateName('base-private')
+        ..setMiddlePrivateName('middle-private')
+        ..setMixinCount(9)
+        ..publicLabel = 'base-public'
+        ..publicMiddleCount = 12
+        ..childMutable = 'child';
+  final result = fory.deserialize<CrossLibraryChild>(fory.serialize(input));
+  if (result.ownerValue != 'base-final' ||
+      result.middleValue != 37 ||
+      !result.childFinal ||
+      result.basePrivateName != 'base-private' ||
+      result.middlePrivateName != 'middle-private' ||
+      result.mixinCount != 9 ||
+      result.publicLabel != 'base-public' ||
+      result.publicMiddleCount != 12 ||
+      result.childMutable != 'child') {
+    throw StateError('Inherited structural serializer round trip failed.');
+  }
+}
diff --git a/dart/packages/fory/CHANGELOG.md b/dart/packages/fory/CHANGELOG.md
index eeb9729eb..a7f2566b5 100644
--- a/dart/packages/fory/CHANGELOG.md
+++ b/dart/packages/fory/CHANGELOG.md
@@ -6,6 +6,18 @@
 - Replace `ForyField.skip` with `ForyField.ignore`, remove `skip` from container
   field annotations, and include ignored and discoverable external target
   fields in graph-memory accounting.
+- Flatten ordinary `ForyStruct` superclass and applied-mixin storage into the
+  concrete child's generated schema. Public and same-library private fields use
+  direct access; cross-library private fields require
+  `exposePrivateFields: true` on a public boundary in each declaring library.
+- Reject hidden, inaccessible, unsupported, or unconstructable inherited
+  storage instead of omitting it. Non-ignored `final` and `late final` fields
+  now require a proven identity-preserving constructor path.
+- This inheritance correction is a breaking generated-schema change. Regenerate
+  all affected `.fory.dart` files; fixed-schema payloads written when inherited
+  fields were omitted are not supported by a compatibility reader. External
+  target declarations remain explicit, and inheritance does not change the
+  runtime reference protocol.
 
 ## 1.4.0
 
diff --git a/dart/packages/fory/README.md b/dart/packages/fory/README.md
index 9b4206790..cda678654 100644
--- a/dart/packages/fory/README.md
+++ b/dart/packages/fory/README.md
@@ -11,6 +11,7 @@ cases.
 - Cross-language serialization with the Fory xlang format
 - Dart VM/AOT, Flutter, and web platform support
 - Generated serializers for annotated structs and enums
+- Flattened superclass and mixin storage for ordinary generated structs
 - External structural serializers for classes owned by another package
 - Compatible mode for schema evolution
 - Optional reference tracking for shared and circular object graphs
@@ -87,9 +88,73 @@ void main() {
 Generate the companion file before running the program:
 
 ```bash
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
 
+## Ordinary Struct Inheritance
+
+An ordinary `@ForyStruct()` includes all instance storage from its concrete
+superclass and applied-mixin chain. The concrete child has one flattened schema
+and one generated serializer; parent fields are globally ordered with child
+fields rather than encoded as a nested parent object.
+
+Public inherited fields and private inherited fields declared in the same Dart
+library need no annotation on the parent. Field metadata stays with the field
+declaration, and `@ForyField(ignore: true)` on that declaration is the only way
+to omit storage from the generated schema.
+
+Private fields declared in another Dart library require an explicit opt-in from
+that declaring library:
+
+```dart
+// package:model_owner/base.dart
+import 'package:fory/fory.dart';
+
+part 'base.fory.dart';
+
+@ForyStruct(exposePrivateFields: true)
+abstract class AccountBase {
+  AccountBase(String tenantId) : _tenantId = tenantId;
+
+  final String _tenantId;
+
+  String get tenantId => _tenantId;
+}
+```
+
+The concrete child uses the normal annotation:
+
+```dart
+// lib/account.dart
+import 'package:fory/fory.dart';
+import 'package:model_owner/base.dart';
+
+part 'account.fory.dart';
+
+@ForyStruct()
+final class Account extends AccountBase {
+  Account(String tenantId) : super(tenantId);
+}
+```
+
+Run code generation in the package that declares `AccountBase` before building
+a dependent package. The provider's published source must include its generated
+`.fory.dart` part. A barrel import is also valid when it re-exports both the
+public boundary and its generated access companion.
+
+`exposePrivateFields` controls only cross-library access to private state. It
+does not enable field discovery, affect same-library private fields, or grant a
+child permission to expose private state owned by another library. If private
+fields come from multiple libraries, each declaring library must opt in
+independently.
+
+Every non-ignored `final` or `late final` field must receive its decoded value
+unchanged through the concrete child's generative constructor chain. Fory
+accepts initializing formals, super formals, redirects, and direct constructor
+initializers that preserve the exact field value. A matching parameter name
+alone is not sufficient. Generation fails for inaccessible, hidden,
+unsupported, or unconstructable storage instead of silently dropping it.
+
 ## External-Type Serialization
 
 Use `ForyStruct.target` when another package owns a class whose public schema
@@ -131,6 +196,9 @@ final decoded = fory.deserialize<third_party.User>(bytes);
 Fields and nested `List`, `Set`, and `Map` values use the same target
 registration. Select a public named generative constructor with
 `constructor: 'name'` when the unnamed constructor is not appropriate.
+External declarations remain explicit schemas: they may list an accessible
+inherited target property, but Fory does not scan the external target's
+hierarchy automatically, and `exposePrivateFields` is not valid with `target`.
 
 ## Type Registration
 
@@ -198,17 +266,20 @@ class NodeList {
 }
 ```
 
+Inherited field metadata enters this same reference analysis. Inheritance does
+not add a second reference owner or change the runtime reference protocol.
+
 ## Field Annotations
 
 `@ForyField()` controls per-field serialization behavior:
 
-| Option     | Description                                      |
-| ---------- | ------------------------------------------------ |
-| `ignore`   | Exclude the field from serialization             |
-| `id`       | Stable field ID for compatible-mode evolution    |
-| `nullable` | Override nullability inference                   |
-| `ref`      | Enable reference tracking for this field         |
-| `dynamic`  | Control whether runtime type metadata is written |
+| Option     | Description                                         |
+| ---------- | --------------------------------------------------- |
+| `ignore`   | Exclude the declaring storage field from the schema |
+| `id`       | Stable field ID for compatible-mode evolution       |
+| `nullable` | Override nullability inference                      |
+| `ref`      | Enable reference tracking for this field            |
+| `dynamic`  | Control whether runtime type metadata is written    |
 
 `type:` is the canonical override surface for nested field semantics:
 
@@ -224,7 +295,8 @@ Map<String, List<int?>> nested = <String, List<int?>>{};
 ## Manual Serializers
 
 Use `Serializer<T>` when a type needs custom wire behavior, field conversion,
-or construction that an external structural serializer cannot express.
+or construction that generated ordinary or external structural serializers
+cannot prove.
 
 ```dart
 import 'package:fory/fory.dart';
@@ -321,8 +393,9 @@ The main exported API includes:
 
 - `Fory` — main serialization facade
 - `Config` — Fory configuration
-- `ForyStruct`, including `target` and `constructor`, plus `ForyField`,
-  `ListField`, `SetField`, and `MapField` — struct annotations
+- `ForyStruct`, including `target`, `constructor`, and
+  `exposePrivateFields`, plus `ForyField`, `ListField`, `SetField`, and
+  `MapField` — struct annotations
 - `ForyUnion` — union type annotation
 - `Serializer`, `UnionSerializer`, `EnumSerializer` — serializer base classes
 - `Buffer`, `WriteContext`, `ReadContext` — low-level I/O
diff --git a/dart/packages/fory/lib/src/annotation/fory_field.dart b/dart/packages/fory/lib/src/annotation/fory_field.dart
index 6df0a0928..a9b82e51a 100644
--- a/dart/packages/fory/lib/src/annotation/fory_field.dart
+++ b/dart/packages/fory/lib/src/annotation/fory_field.dart
@@ -19,14 +19,35 @@
 
 import 'package:fory/src/annotation/type_spec.dart';
 
+/// Configures serialization for the storage field on which it is declared.
+///
+/// Field metadata remains attached to its declaration when an ordinary
+/// `ForyStruct` inherits the field. Every discovered ordinary storage field is
+/// serialized unless that declaring field sets [ignore] to `true`.
 final class ForyField {
+  /// Whether to omit this field from the serialized schema.
+  ///
+  /// This is the only supported way to omit an ordinary instance storage
+  /// field. Ignored storage still contributes to shallow graph-memory
+  /// accounting.
   final bool ignore;
+
+  /// The stable numeric field identity used for schema evolution.
   final int? id;
+
+  /// An optional override for the field's inferred nullability.
   final bool? nullable;
+
+  /// Whether references through this field preserve object identity.
   final bool ref;
+
+  /// Whether values carry their concrete runtime type.
   final bool? dynamic;
+
+  /// An optional exact wire-type description.
   final TypeSpec? type;
 
+  /// Creates field-level serialization metadata.
   const ForyField({
     this.ignore = false,
     this.id,
diff --git a/dart/packages/fory/lib/src/annotation/fory_struct.dart b/dart/packages/fory/lib/src/annotation/fory_struct.dart
index 2812cfc7b..786aab085 100644
--- a/dart/packages/fory/lib/src/annotation/fory_struct.dart
+++ b/dart/packages/fory/lib/src/annotation/fory_struct.dart
@@ -17,7 +17,7 @@
  * under the License.
  */
 
-/// Configures Fory struct code generation for the annotated class or [target].
+/// Configures Fory generation for an ordinary class or mixin, or for [target].
 final class ForyStruct {
   /// Whether the generated struct should use evolving field metadata.
   ///
@@ -39,6 +39,20 @@ final class ForyStruct {
   /// with [target].
   final String? constructor;
 
+  /// Whether this library exposes private hierarchy storage to child libraries.
+  ///
+  /// This option is valid only on a public ordinary hierarchy boundary. It
+  /// generates typed static access methods for non-ignored private storage
+  /// declared by this library and inherited through the boundary. It does not
+  /// control field discovery or same-library private access, and it cannot be
+  /// used with [target].
+  final bool exposePrivateFields;
+
   /// Creates struct-level generation options.
-  const ForyStruct({this.evolving = true, this.target, this.constructor});
+  const ForyStruct({
+    this.evolving = true,
+    this.target,
+    this.constructor,
+    this.exposePrivateFields = false,
+  });
 }
diff --git a/dart/packages/fory/lib/src/codegen/fory_constructor_analysis.dart b/dart/packages/fory/lib/src/codegen/fory_constructor_analysis.dart
new file mode 100644
index 000000000..d817a983e
--- /dev/null
+++ b/dart/packages/fory/lib/src/codegen/fory_constructor_analysis.dart
@@ -0,0 +1,874 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+part of 'fory_generator.dart';
+
+final class _OrdinaryConstructorAnalyzer {
+  final BuildStep buildStep;
+
+  final Map<ConstructorElement, Future<AstNode?>> _astByConstructor =
+      Map<ConstructorElement, Future<AstNode?>>.identity();
+  final List<_ConstructorFrame> _frames = <_ConstructorFrame>[];
+
+  _OrdinaryConstructorAnalyzer({required this.buildStep});
+
+  Future<_ConstructionModel> build({
+    required ClassElement declaration,
+    required String targetTypeLiteral,
+    required ConstructorElement constructor,
+    required List<_GeneratedFieldSpec> fields,
+  }) async {
+    if (constructor.formalParameters.isEmpty) {
+      var mutableConstruction = true;
+      for (final field in fields) {
+        if (!field.writable) {
+          mutableConstruction = false;
+          break;
+        }
+      }
+      if (mutableConstruction) {
+        return const _ConstructionModel.mutable(constructorName: null);
+      }
+    }
+
+    for (final field in fields) {
+      if (field.declaration.isFinal && field.declaration.hasInitializer) {
+        throw InvalidGenerationSourceError(
+          'Final field ${_fieldLabel(field)} has a declaration initializer '
+          'and cannot receive its decoded value from '
+          '$targetTypeLiteral(). Mark the field with '
+          '@ForyField(ignore: true), remove the initializer and add an '
+          'identity-preserving constructor path, or use a manual serializer.',
+          element: field.declaration,
+        );
+      }
+    }
+
+    _frames.clear();
+    final active = <_ConstructorKey>{};
+    final rootFrame = await _buildFrame(
+      constructor,
+      fields,
+      declaration,
+      active,
+    );
+    final reachableByRoot = <_ParameterNode, Set<_GeneratedFieldSpec>>{
+      for (final root in rootFrame.parameters) root: _reachableFields(root),
+    };
+    final bindingByRoot = <_ParameterNode, _GeneratedFieldSpec>{};
+    final rootByArgumentField =
+        Map<_GeneratedFieldSpec, _ParameterNode>.identity();
+
+    for (final field in fields) {
+      if (field.writable) {
+        continue;
+      }
+      _ParameterNode? root;
+      for (final candidate in rootFrame.parameters) {
+        if (!reachableByRoot[candidate]!.contains(field)) {
+          continue;
+        }
+        if (root != null) {
+          throw InvalidGenerationSourceError(
+            'Final field ${_fieldLabel(field)} is reached by multiple '
+            'parameters of $targetTypeLiteral(). A serialized final field '
+            'must have exactly one identity-preserving constructor root.',
+            element: field.declaration,
+          );
+        }
+        root = candidate;
+      }
+      if (root == null) {
+        _throwMissingFinalFlow(declaration, targetTypeLiteral, field);
+      }
+      final existing = bindingByRoot[root];
+      if (existing != null && !identical(existing, field)) {
+        throw InvalidGenerationSourceError(
+          'Constructor parameter ${root.parameter.displayName} on '
+          '$targetTypeLiteral initializes multiple non-writable fields '
+          '${_fieldLabel(existing)} and ${_fieldLabel(field)}. One decoded '
+          'constructor argument may restore at most one final field.',
+          element: declaration,
+        );
+      }
+      bindingByRoot[root] = field;
+      rootByArgumentField[field] = root;
+    }
+
+    for (final root in rootFrame.parameters) {
+      if (bindingByRoot.containsKey(root)) {
+        continue;
+      }
+      _GeneratedFieldSpec? candidate;
+      List<String>? ambiguousLabels;
+      for (final field in reachableByRoot[root]!) {
+        if (!field.writable) {
+          continue;
+        }
+        if (candidate == null) {
+          candidate = field;
+          continue;
+        }
+        ambiguousLabels ??= <String>[_fieldLabel(candidate)];
+        ambiguousLabels.add(_fieldLabel(field));
+      }
+      if (candidate == null) {
+        if (root.parameter.isRequired) {
+          _throwMissingRequiredSource(
+            declaration,
+            targetTypeLiteral,
+            root,
+            fields,
+          );
+        }
+        continue;
+      }
+      if (ambiguousLabels != null) {
+        throw InvalidGenerationSourceError(
+          'Constructor parameter ${root.parameter.displayName} on '
+          '$targetTypeLiteral has multiple writable identity-flow sources: '
+          '${ambiguousLabels.join(', ')}. Select a constructor '
+          'whose parameter flow identifies exactly one serialized field.',
+          element: declaration,
+        );
+      }
+      final field = candidate;
+      final existingRoot = rootByArgumentField[field];
+      if (existingRoot != null && !identical(existingRoot, root)) {
+        throw InvalidGenerationSourceError(
+          'Serialized field ${_fieldLabel(field)} would supply constructor '
+          'parameters ${existingRoot.parameter.displayName} and '
+          '${root.parameter.displayName} on $targetTypeLiteral. A field may '
+          'supply at most one constructor argument.',
+          element: field.declaration,
+        );
+      }
+      bindingByRoot[root] = field;
+      rootByArgumentField[field] = root;
+    }
+
+    final arguments = <_ConstructorArgumentSpec>[];
+    var omittedOptionalPositional = false;
+    for (final root in rootFrame.parameters) {
+      final parameter = root.parameter;
+      final field = bindingByRoot[root];
+      if (field == null) {
+        if (parameter.isOptionalPositional) {
+          omittedOptionalPositional = true;
+        }
+        continue;
+      }
+      if (parameter.isPositional && omittedOptionalPositional) {
+        throw InvalidGenerationSourceError(
+          'Constructor $targetTypeLiteral() cannot pass parameter '
+          '${parameter.displayName} after an omitted optional positional '
+          'parameter. Use named parameters, remove the positional gap, mark '
+          'the final field with @ForyField(ignore: true), or use a manual '
+          'serializer.',
+          element: declaration,
+        );
+      }
+      arguments.add(
+        _ConstructorArgumentSpec(
+          field: field,
+          parameterName: parameter.displayName,
+          named: parameter.isNamed,
+        ),
+      );
+    }
+
+    if (bindingByRoot.isEmpty) {
+      return const _ConstructionModel.mutable(constructorName: null);
+    }
+
+    final postConstructionFields = Set<_GeneratedFieldSpec>.identity();
+    for (final field in fields) {
+      if (field.writable && !rootByArgumentField.containsKey(field)) {
+        postConstructionFields.add(field);
+      }
+    }
+    return _ConstructionModel.constructor(
+      constructorName: null,
+      arguments: arguments,
+      postConstructionFields: postConstructionFields,
+    );
+  }
+
+  Future<_ConstructorFrame> _buildFrame(
+    ConstructorElement constructor,
+    List<_GeneratedFieldSpec> fields,
+    ClassElement declaration,
+    Set<_ConstructorKey> active,
+  ) async {
+    final key = _ConstructorKey(
+      constructor.baseElement,
+      constructor.returnType,
+    );
+    if (active.contains(key)) {
+      throw InvalidGenerationSourceError(
+        'Constructor redirection or super-constructor flow for '
+        '${declaration.displayName} is cyclic at '
+        '${_constructorLabel(constructor)}.',
+        element: declaration,
+      );
+    }
+    final frame = _ConstructorFrame(constructor);
+    _frames.add(frame);
+    active.add(key);
+    try {
+      final redirected = constructor.redirectedConstructor;
+      final superConstructor = constructor.superConstructor;
+      if (redirected != null && superConstructor != null) {
+        throw InvalidGenerationSourceError(
+          'Constructor ${_constructorLabel(constructor)} resolves both a '
+          'redirect target and a super constructor, so Fory cannot prove one '
+          'construction path.',
+          element: declaration,
+        );
+      }
+      final nextConstructor = redirected ?? superConstructor;
+      final nextFrame =
+          nextConstructor == null || _isObjectConstructor(nextConstructor)
+              ? null
+              : await _buildFrame(nextConstructor, fields, declaration, active);
+
+      _addSummaryEdges(frame, nextFrame, fields);
+      if (_isMixinApplicationConstructor(constructor)) {
+        _addMixinForwardingEdges(frame, nextFrame, declaration);
+      } else if (_isDeclaredConstructor(constructor)) {
+        // Summary elements completely prove a leaf constructor only when it
+        // has no optional parameters and every required argument and final
+        // field already has an exact storage edge. An explicit initializer
+        // may connect an optional parameter to mutable storage that must
+        // receive its decoded value during construction.
+        if (nextFrame != null || !_summaryProvesLeaf(frame, fields)) {
+          final node = await _astFor(constructor);
+          if (node is ConstructorDeclaration) {
+            _addAstEdges(
+              frame,
+              nextFrame,
+              redirected != null
+                  ? _ConstructorHop.redirect
+                  : _ConstructorHop.superCall,
+              node,
+              fields,
+            );
+          } else {
+            frame.unavailableProof =
+                node == null
+                    ? 'resolved source AST is unavailable'
+                    : 'resolved node ${node.runtimeType} is not a supported '
+                        'constructor declaration';
+          }
+        }
+      } else if (frame.parameters.isNotEmpty) {
+        frame.unavailableProof =
+            'synthetic constructor parameters do not expose an exact '
+            'forwarding relationship';
+      }
+      return frame;
+    } finally {
+      active.remove(key);
+    }
+  }
+
+  bool _summaryProvesLeaf(
+    _ConstructorFrame frame,
+    List<_GeneratedFieldSpec> fields,
+  ) {
+    for (final parameter in frame.parameters) {
+      if (parameter.parameter.isOptional || parameter.fieldTargets.isEmpty) {
+        return false;
+      }
+    }
+    for (final field in fields) {
+      if (field.writable) {
+        continue;
+      }
+      var hasFieldTarget = false;
+      for (final parameter in frame.parameters) {
+        if (parameter.fieldTargets.contains(field)) {
+          hasFieldTarget = true;
+          break;
+        }
+      }
+      if (!hasFieldTarget) {
+        return false;
+      }
+    }
+    return true;
+  }
+
+  void _addSummaryEdges(
+    _ConstructorFrame frame,
+    _ConstructorFrame? nextFrame,
+    List<_GeneratedFieldSpec> fields,
+  ) {
+    for (final source in frame.parameters) {
+      final parameter = source.parameter;
+      if (parameter is FieldFormalParameterElement) {
+        final fieldElement = parameter.field;
+        if (fieldElement == null) {
+          frame.recordIssue(
+            'field formal ${parameter.displayName} has no resolved storage '
+            'field',
+          );
+        } else {
+          final field = _fieldFor(fieldElement, frame.constructor, fields);
+          if (field != null) {
+            _addFieldEdge(frame, source, field);
+          }
+        }
+      }
+      if (parameter is SuperFormalParameterElement) {
+        final targetElement = parameter.superConstructorParameter;
+        if (nextFrame == null || targetElement == null) {
+          frame.recordIssue(
+            'super formal ${parameter.displayName} has no resolved target '
+            'parameter',
+          );
+          continue;
+        }
+        final target = nextFrame.parameterFor(targetElement);
+        if (target == null) {
+          frame.recordIssue(
+            'super formal ${parameter.displayName} does not identify a '
+            'parameter on ${_constructorLabel(nextFrame.constructor)}',
+          );
+          continue;
+        }
+        _addParameterEdge(frame, source, target);
+      }
+    }
+  }
+
+  void _addMixinForwardingEdges(
+    _ConstructorFrame frame,
+    _ConstructorFrame? nextFrame,
+    ClassElement declaration,
+  ) {
+    if (nextFrame == null ||
+        frame.parameters.length != nextFrame.parameters.length) {
+      throw InvalidGenerationSourceError(
+        'Synthetic mixin-application constructor '
+        '${_constructorLabel(frame.constructor)} does not have the exact '
+        'forwarding signature of its super constructor.',
+        element: declaration,
+      );
+    }
+    for (var index = 0; index < frame.parameters.length; index += 1) {
+      final source = frame.parameters[index];
+      final target = nextFrame.parameters[index];
+      final sourceParameter = source.parameter;
+      final targetParameter = target.parameter;
+      final sameKind =
+          sourceParameter.isNamed == targetParameter.isNamed &&
+          sourceParameter.isRequired == targetParameter.isRequired &&
+          sourceParameter.isOptional == targetParameter.isOptional &&
+          sourceParameter.isPositional == targetParameter.isPositional;
+      final sameName =
+          !sourceParameter.isNamed ||
+          sourceParameter.displayName == targetParameter.displayName;
+      if (!sameKind ||
+          !sameName ||
+          sourceParameter.type != targetParameter.type) {
+        throw InvalidGenerationSourceError(
+          'Synthetic mixin-application constructor parameter '
+          '${sourceParameter.displayName} on '
+          '${_constructorLabel(frame.constructor)} does not exactly forward '
+          'the corresponding super-constructor parameter.',
+          element: declaration,
+        );
+      }
+      source.addParameterTarget(target);
+    }
+  }
+
+  void _addAstEdges(
+    _ConstructorFrame frame,
+    _ConstructorFrame? nextFrame,
+    _ConstructorHop hop,
+    ConstructorDeclaration node,
+    List<_GeneratedFieldSpec> fields,
+  ) {
+    final declaredConstructor = node.declaredFragment?.element;
+    if (declaredConstructor == null ||
+        !identical(
+          declaredConstructor.baseElement,
+          frame.constructor.baseElement,
+        )) {
+      frame.recordIssue(
+        'resolved constructor AST does not identify '
+        '${_constructorLabel(frame.constructor)}',
+      );
+      return;
+    }
+
+    RedirectingConstructorInvocation? redirectInvocation;
+    SuperConstructorInvocation? superInvocation;
+    for (final initializer in node.initializers) {
+      if (initializer is ConstructorFieldInitializer) {
+        _addFieldInitializerEdge(frame, initializer, fields);
+      } else if (initializer is RedirectingConstructorInvocation) {
+        if (redirectInvocation != null) {
+          frame.recordIssue('multiple redirecting constructor invocations');
+        }
+        redirectInvocation = initializer;
+      } else if (initializer is SuperConstructorInvocation) {
+        if (superInvocation != null) {
+          frame.recordIssue('multiple super-constructor invocations');
+        }
+        superInvocation = initializer;
+      }
+    }
+
+    if (nextFrame == null) {
+      return;
+    }
+    switch (hop) {
+      case _ConstructorHop.redirect:
+        final invocation = redirectInvocation;
+        if (invocation == null) {
+          frame.recordIssue(
+            'redirect target ${_constructorLabel(nextFrame.constructor)} '
+            'requires an explicit resolved invocation',
+          );
+          return;
+        }
+        _addInvocationEdges(
+          frame,
+          nextFrame,
+          invocation.element,
+          invocation.argumentList.arguments,
+        );
+      case _ConstructorHop.superCall:
+        final invocation = superInvocation;
+        if (invocation == null) {
+          return;
+        }
+        _addInvocationEdges(
+          frame,
+          nextFrame,
+          invocation.element,
+          invocation.argumentList.arguments,
+        );
+    }
+  }
+
+  void _addFieldInitializerEdge(
+    _ConstructorFrame frame,
+    ConstructorFieldInitializer initializer,
+    List<_GeneratedFieldSpec> fields,
+  ) {
+    final fieldElement = initializer.fieldName.element;
+    if (fieldElement is! FieldElement) {
+      frame.recordIssue(
+        'field initializer ${initializer.fieldName.name} has no resolved '
+        'storage field',
+      );
+      return;
+    }
+    final field = _fieldFor(fieldElement, frame.constructor, fields);
+    if (field == null) {
+      return;
+    }
+    final source = _directParameter(initializer.expression, frame);
+    if (source == null) {
+      if (!field.writable) {
+        frame.recordIssue(
+          'initializer for ${_fieldLabel(field)} transforms or does not '
+          'directly reference a constructor parameter',
+        );
+      }
+      return;
+    }
+    _addFieldEdge(frame, source, field);
+  }
+
+  void _addInvocationEdges(
+    _ConstructorFrame frame,
+    _ConstructorFrame targetFrame,
+    ConstructorElement? invokedConstructor,
+    Iterable<AstNode> arguments,
+  ) {
+    if (invokedConstructor == null ||
+        !identical(
+          invokedConstructor.baseElement,
+          targetFrame.constructor.baseElement,
+        )) {
+      frame.recordIssue(
+        'resolved invocation does not identify '
+        '${_constructorLabel(targetFrame.constructor)}',
+      );
+      return;
+    }
+    var positionalIndex = 0;
+    for (final argument in arguments) {
+      final firstToken = argument.beginToken;
+      final named = firstToken.next?.lexeme == ':';
+      final target = _invocationTarget(
+        targetFrame,
+        firstToken.lexeme,
+        named,
+        positionalIndex,
+      );
+      if (!named) {
+        positionalIndex += 1;
+      }
+      if (target == null) {
+        frame.recordIssue(
+          'constructor argument has no resolved target parameter',
+        );
+        continue;
+      }
+      final value = _invocationValue(argument, named);
+      if (value == null) {
+        frame.recordIssue(
+          'constructor argument has no resolved value expression',
+        );
+        continue;
+      }
+      final source = _directParameter(value, frame);
+      if (source == null) {
+        frame.recordIssue(
+          'argument for ${target.parameter.displayName} transforms or does '
+          'not directly reference a constructor parameter',
+        );
+        continue;
+      }
+      _addParameterEdge(frame, source, target);
+    }
+  }
+
+  _ParameterNode? _invocationTarget(
+    _ConstructorFrame frame,
+    String argumentName,
+    bool named,
+    int positionalIndex,
+  ) {
+    var currentPositionalIndex = 0;
+    for (final parameter in frame.parameters) {
+      if (named) {
+        if (parameter.parameter.isNamed &&
+            parameter.parameter.displayName == argumentName) {
+          return parameter;
+        }
+        continue;
+      }
+      if (!parameter.parameter.isPositional) {
+        continue;
+      }
+      if (currentPositionalIndex == positionalIndex) {
+        return parameter;
+      }
+      currentPositionalIndex += 1;
+    }
+    return null;
+  }
+
+  Expression? _invocationValue(AstNode argument, bool named) {
+    if (!named) {
+      return argument is Expression ? argument : null;
+    }
+    // Supported analyzer versions use different public node types for named
+    // arguments. Dart grammar still gives both representations exactly one
+    // direct value expression after the name and colon.
+    Expression? value;
+    for (final child in argument.childEntities) {
+      if (child is! Expression) {
+        continue;
+      }
+      if (value != null) {
+        return null;
+      }
+      value = child;
+    }
+    return value;
+  }
+
+  void _addFieldEdge(
+    _ConstructorFrame frame,
+    _ParameterNode source,
+    _GeneratedFieldSpec field,
+  ) {
+    if (source.parameter.type != field.type) {
+      frame.recordIssue(
+        'parameter ${source.parameter.displayName} has type '
+        '${source.parameter.type.getDisplayString()}, but '
+        '${_fieldLabel(field)} has type ${field.type.getDisplayString()}',
+      );
+      return;
+    }
+    source.addFieldTarget(field);
+  }
+
+  void _addParameterEdge(
+    _ConstructorFrame frame,
+    _ParameterNode source,
+    _ParameterNode target,
+  ) {
+    if (source.parameter.type != target.parameter.type) {
+      frame.recordIssue(
+        'parameter ${source.parameter.displayName} on '
+        '${_constructorLabel(frame.constructor)} has type '
+        '${source.parameter.type.getDisplayString()}, but target parameter '
+        '${target.parameter.displayName} has type '
+        '${target.parameter.type.getDisplayString()}',
+      );
+      return;
+    }
+    source.addParameterTarget(target);
+  }
+
+  _GeneratedFieldSpec? _fieldFor(
+    FieldElement fieldElement,
+    ConstructorElement constructor,
+    List<_GeneratedFieldSpec> fields,
+  ) {
+    final fieldBase = fieldElement.baseElement;
+    final ownerBase = fieldBase.enclosingElement.baseElement;
+    final constructorOwner = constructor.enclosingElement.baseElement;
+    _GeneratedFieldSpec? match;
+    for (final field in fields) {
+      if (!identical(field.declaration.baseElement, fieldBase) ||
+          !identical(
+            field.declaration.enclosingElement.baseElement,
+            ownerBase,
+          ) ||
+          !identical(ownerBase, constructorOwner)) {
+        continue;
+      }
+      if (match != null) {
+        throw InvalidGenerationSourceError(
+          'Constructor ${_constructorLabel(constructor)} cannot identify one '
+          'applied storage slot for ${fieldBase.displayName}.',
+          element: fieldBase,
+        );
+      }
+      match = field;
+    }
+    return match;
+  }
+
+  _ParameterNode? _directParameter(
+    Expression expression,
+    _ConstructorFrame frame,
+  ) {
+    final unwrapped = expression.unParenthesized;
+    if (unwrapped is! SimpleIdentifier) {
+      return null;
+    }
+    final element = unwrapped.element;
+    return element is FormalParameterElement
+        ? frame.parameterFor(element)
+        : null;
+  }
+
+  Set<_GeneratedFieldSpec> _reachableFields(_ParameterNode root) {
+    final fields = Set<_GeneratedFieldSpec>.identity();
+    final visited = Set<_ParameterNode>.identity();
+
+    void visit(_ParameterNode parameter) {
+      if (!visited.add(parameter)) {
+        return;
+      }
+      fields.addAll(parameter.fieldTargets);
+      for (final target in parameter.parameterTargets) {
+        visit(target);
+      }
+    }
+
+    visit(root);
+    return fields;
+  }
+
+  Future<AstNode?> _astFor(ConstructorElement constructor) {
+    final base = constructor.baseElement;
+    final cached = _astByConstructor[base];
+    if (cached != null) {
+      return cached;
+    }
+    final ast = buildStep.resolver.astNodeFor(
+      base.firstFragment,
+      resolve: true,
+    );
+    _astByConstructor[base] = ast;
+    return ast;
+  }
+
+  Never _throwMissingFinalFlow(
+    ClassElement declaration,
+    String targetTypeLiteral,
+    _GeneratedFieldSpec field,
+  ) {
+    final proofProblem = _firstProofProblem();
+    final detail =
+        proofProblem == null
+            ? ''
+            : ' Constructor proof could not use $proofProblem.';
+    throw InvalidGenerationSourceError(
+      'Final field ${_fieldLabel(field)} is not initialized from a parameter '
+      'of selected constructor $targetTypeLiteral(). A serialized final field '
+      'must receive its decoded value unchanged through a field formal, '
+      'constructor initializer, redirect, or super-constructor forwarding.'
+      '$detail Mark the field with @ForyField(ignore: true), add an exact '
+      'identity path, or use a manual serializer.',
+      element: field.declaration,
+    );
+  }
+
+  Never _throwMissingRequiredSource(
+    ClassElement declaration,
+    String targetTypeLiteral,
+    _ParameterNode root,
+    List<_GeneratedFieldSpec> fields,
+  ) {
+    _GeneratedFieldSpec? sameName;
+    for (final field in fields) {
+      if (field.name == root.parameter.displayName && field.writable) {
+        sameName = field;
+        break;
+      }
+    }
+    final nameDetail =
+        sameName == null
+            ? ''
+            : ' A same-name field ${_fieldLabel(sameName)} exists, but name '
+                'equality is not constructor identity proof.';
+    final proofProblem = _firstProofProblem();
+    final proofDetail =
+        proofProblem == null
+            ? ''
+            : ' Constructor proof could not use $proofProblem.';
+    throw InvalidGenerationSourceError(
+      'Required constructor parameter ${root.parameter.displayName} on '
+      '$targetTypeLiteral has no serialized field connected by an exact '
+      'identity-preserving path.$nameDetail$proofDetail Add a direct '
+      'parameter-to-field flow, select a zero-required-argument constructor, '
+      'or use a manual serializer.',
+      element: declaration,
+    );
+  }
+
+  String? _firstProofProblem() {
+    String? firstIssue;
+    for (final frame in _frames) {
+      final unavailable = frame.unavailableProof;
+      if (unavailable != null) {
+        return '$unavailable for ${_constructorLabel(frame.constructor)}';
+      }
+      final issue = frame.firstIssue;
+      if (firstIssue == null && issue != null) {
+        firstIssue = '$issue in ${_constructorLabel(frame.constructor)}';
+      }
+    }
+    return firstIssue;
+  }
+
+  bool _isDeclaredConstructor(ConstructorElement constructor) =>
+      constructor.baseElement.nonSynthetic is ConstructorElement;
+
+  bool _isMixinApplicationConstructor(ConstructorElement constructor) {
+    final owner = constructor.enclosingElement;
+    return owner is ClassElement && owner.isMixinApplication;
+  }
+
+  bool _isObjectConstructor(ConstructorElement constructor) {
+    final owner = constructor.enclosingElement;
+    return owner is ClassElement && owner.isDartCoreObject;
+  }
+
+  String _constructorLabel(ConstructorElement constructor) =>
+      '${constructor.enclosingElement.displayName}.'
+      '${constructor.displayName}';
+
+  String _fieldLabel(_GeneratedFieldSpec field) =>
+      '${field.declaration.enclosingElement.displayName}.${field.name}';
+}
+
+enum _ConstructorHop { redirect, superCall }
+
+final class _ConstructorKey {
+  final ConstructorElement declaration;
+  final InterfaceType returnType;
+
+  const _ConstructorKey(this.declaration, this.returnType);
+
+  @override
+  int get hashCode => Object.hash(identityHashCode(declaration), returnType);
+
+  @override
+  bool operator ==(Object other) =>
+      other is _ConstructorKey &&
+      identical(declaration, other.declaration) &&
+      returnType == other.returnType;
+}
+
+final class _ConstructorFrame {
+  final ConstructorElement constructor;
+  final List<_ParameterNode> parameters;
+  String? firstIssue;
+  String? unavailableProof;
+
+  _ConstructorFrame(this.constructor)
+    : parameters = <_ParameterNode>[
+        for (final parameter in constructor.formalParameters)
+          _ParameterNode(parameter),
+      ];
+
+  void recordIssue(String issue) {
+    firstIssue ??= issue;
+  }
+
+  _ParameterNode? parameterFor(FormalParameterElement element) {
+    final base = element.baseElement;
+    for (final parameter in parameters) {
+      if (identical(parameter.parameter.baseElement, base)) {
+        return parameter;
+      }
+    }
+    return null;
+  }
+}
+
+final class _ParameterNode {
+  final FormalParameterElement parameter;
+  final List<_ParameterNode> parameterTargets = <_ParameterNode>[];
+  final List<_GeneratedFieldSpec> fieldTargets = <_GeneratedFieldSpec>[];
+
+  _ParameterNode(this.parameter);
+
+  void addParameterTarget(_ParameterNode target) {
+    for (final existing in parameterTargets) {
+      if (identical(existing, target)) {
+        return;
+      }
+    }
+    parameterTargets.add(target);
+  }
+
+  void addFieldTarget(_GeneratedFieldSpec target) {
+    for (final existing in fieldTargets) {
+      if (identical(existing, target)) {
+        return;
+      }
+    }
+    fieldTargets.add(target);
+  }
+}
diff --git a/dart/packages/fory/lib/src/codegen/fory_generator.dart b/dart/packages/fory/lib/src/codegen/fory_generator.dart
index 710c02b90..619b5202e 100644
--- a/dart/packages/fory/lib/src/codegen/fory_generator.dart
+++ b/dart/packages/fory/lib/src/codegen/fory_generator.dart
@@ -18,7 +18,9 @@
  */
 
 import 'dart:async';
+import 'dart:convert';
 
+import 'package:analyzer/dart/ast/ast.dart';
 import 'package:analyzer/dart/constant/value.dart';
 import 'package:analyzer/dart/element/element.dart';
 import 'package:analyzer/dart/element/nullability_suffix.dart';
@@ -27,6 +29,8 @@ import 'package:build/build.dart';
 import 'package:fory/fory.dart';
 import 'package:source_gen/source_gen.dart';
 
+part 'fory_constructor_analysis.dart';
+
 class DebugGeneratedFieldTypeSpec {
   const DebugGeneratedFieldTypeSpec({
     required this.typeLiteral,
@@ -86,16 +90,42 @@ final class ForyGenerator extends Generator {
     inPackage: 'fory',
   );
 
+  late LibraryElement _sourceLibrary;
   final Map<Element, String?> _importPrefixByElement = <Element, String?>{};
+  final Set<Element> _resolvedImportElements = <Element>{};
+  final Map<InterfaceType, _HierarchyStorage> _hierarchyStorageByType =
+      <InterfaceType, _HierarchyStorage>{};
+  final Map<InterfaceElement, _StructOptions> _structOptionsByElement =
+      <InterfaceElement, _StructOptions>{};
+  Map<LibraryElement, String>? _canonicalLibraryUris;
+
+  ForyGenerator();
+
+  ForyGenerator._forInput();
 
   @override
-  FutureOr<String> generate(LibraryReader library, BuildStep buildStep) {
-    _buildImportPrefixMap(library);
-    final annotatedClasses = library.classes
-        .where((element) => _foryStructChecker.hasAnnotationOf(element))
-        .toList(growable: false);
-    final enumElements = library.enums.toList(growable: false);
-    if (annotatedClasses.isEmpty && enumElements.isEmpty) {
+  Future<String> generate(LibraryReader library, BuildStep buildStep) {
+    return ForyGenerator._forInput()._generate(library, buildStep);
+  }
+
+  Future<String> _generate(LibraryReader library, BuildStep buildStep) async {
+    _sourceLibrary = library.element;
+    final annotatedClasses = <ClassElement>[];
+    for (final element in library.classes) {
+      if (_foryStructChecker.hasAnnotationOf(element)) {
+        annotatedClasses.add(element);
+      }
+    }
+    final annotatedMixins = <MixinElement>[];
+    for (final element in library.element.mixins) {
+      if (_foryStructChecker.hasAnnotationOf(element)) {
+        annotatedMixins.add(element);
+      }
+    }
+    final enumElements = library.enums;
+    if (annotatedClasses.isEmpty &&
+        annotatedMixins.isEmpty &&
+        enumElements.isEmpty) {
       return '';
     }
 
@@ -106,14 +136,58 @@ final class ForyGenerator extends Generator {
     final emitRegistrationHelper =
         !_declaresGeneratedApiOwner(library, buildStep, generatedApiName);
 
-    final enumSpecs = enumElements.map(_analyzeEnum).toList(growable: false);
+    final enumSpecs = <_GeneratedEnumSpec>[];
+    for (final element in enumElements) {
+      enumSpecs.add(_analyzeEnum(element));
+    }
     final structSpecs = <_GeneratedStructSpec>[];
+    final companionSpecs = <_PrivateAccessCompanionSpec>[];
+    final constructorAnalyzer = _OrdinaryConstructorAnalyzer(
+      buildStep: buildStep,
+    );
     for (final element in annotatedClasses) {
-      final structSpec = _analyzeStruct(element);
-      final duplicate =
-          structSpecs
-              .where((existing) => existing.targetType == structSpec.targetType)
-              .firstOrNull;
+      final options = _structOptions(element);
+      final ownsSerializer =
+          options.target != null ||
+          (_ownsOrdinarySerializer(element) && element.typeParameters.isEmpty);
+      if (!ownsSerializer) {
+        _validateProviderOnly(element, options);
+      }
+      if (options.exposePrivateFields) {
+        if (options.target != null) {
+          throw InvalidGenerationSourceError(
+            'ForyStruct.exposePrivateFields cannot be used with '
+            'ForyStruct.target. External structural declarations retain an '
+            'explicit field model.',
+            element: element,
+          );
+        }
+        companionSpecs.add(
+          await _analyzePrivateAccessCompanion(element, buildStep),
+        );
+      }
+      if (!ownsSerializer) {
+        continue;
+      }
+      final annotatedTarget = options.target;
+      final _GeneratedStructSpec structSpec;
+      if (annotatedTarget == null) {
+        structSpec = await _analyzeOrdinaryStruct(
+          element,
+          options,
+          constructorAnalyzer,
+          buildStep,
+        );
+      } else {
+        structSpec = _analyzeExternalStruct(element, options, annotatedTarget);
+      }
+      _GeneratedStructSpec? duplicate;
+      for (final existing in structSpecs) {
+        if (existing.targetType == structSpec.targetType) {
+          duplicate = existing;
+          break;
+        }
+      }
       if (duplicate != null) {
         throw InvalidGenerationSourceError(
           'Fory struct declarations ${duplicate.name} and ${structSpec.name} '
@@ -124,6 +198,13 @@ final class ForyGenerator extends Generator {
       }
       structSpecs.add(structSpec);
     }
+    for (final element in annotatedMixins) {
+      final options = _structOptions(element);
+      _validateProviderOnly(element, options);
+      companionSpecs.add(
+        await _analyzePrivateAccessCompanion(element, buildStep),
+      );
+    }
     final output =
         StringBuffer()
           ..writeln(
@@ -134,17 +215,22 @@ final class ForyGenerator extends Generator {
     for (final enumSpec in enumSpecs) {
       _writeEnum(output, enumSpec);
     }
+    for (final companionSpec in companionSpecs) {
+      _writePrivateAccessCompanion(output, companionSpec);
+    }
     for (final structSpec in structSpecs) {
       _writeStruct(output, structSpec);
     }
 
-    _writeGeneratedSupport(
-      output,
-      enumSpecs: enumSpecs,
-      structSpecs: structSpecs,
-      generatedApiName: generatedApiName,
-      emitRegistrationHelper: emitRegistrationHelper,
-    );
+    if (enumSpecs.isNotEmpty || structSpecs.isNotEmpty) {
+      _writeGeneratedSupport(
+        output,
+        enumSpecs: enumSpecs,
+        structSpecs: structSpecs,
+        generatedApiName: generatedApiName,
+        emitRegistrationHelper: emitRegistrationHelper,
+      );
+    }
     return output.toString();
   }
 
@@ -154,18 +240,26 @@ final class ForyGenerator extends Generator {
     String generatedApiName,
   ) {
     final inputFileName = buildStep.inputId.pathSegments.last;
-    return library.classes.any(
-      (element) =>
-          element.displayName == generatedApiName &&
+    for (final element in library.classes) {
+      if (element.displayName == generatedApiName &&
           element.firstFragment.libraryFragment.source.shortName ==
-              inputFileName,
-    );
+              inputFileName) {
+        return true;
+      }
+    }
+    return false;
   }
 
-  _GeneratedStructSpec _analyzeStruct(ClassElement element) {
+  _StructOptions _structOptions(InterfaceElement element) {
+    final cached = _structOptionsByElement[element];
+    if (cached != null) {
+      return cached;
+    }
     final objectAnnotation = _foryStructChecker.firstAnnotationOf(element);
     final objectReader = ConstantReader(objectAnnotation);
     final evolving = objectReader.peek('evolving')?.boolValue ?? true;
+    final exposePrivateFields =
+        objectReader.peek('exposePrivateFields')?.boolValue ?? false;
     final targetReader = objectReader.peek('target');
     final annotatedTarget =
         targetReader == null || targetReader.isNull
@@ -176,67 +270,233 @@ final class ForyGenerator extends Generator {
         constructorReader == null || constructorReader.isNull
             ? null
             : constructorReader.stringValue;
-    if (annotatedTarget == null && constructorName != null) {
+    final options = _StructOptions(
+      evolving: evolving,
+      target: annotatedTarget,
+      constructorName: constructorName,
+      exposePrivateFields: exposePrivateFields,
+    );
+    _structOptionsByElement[element] = options;
+    return options;
+  }
+
+  bool _ownsOrdinarySerializer(ClassElement element) =>
+      !element.isAbstract && element.isConstructable;
+
+  void _validateProviderOnly(InterfaceElement element, _StructOptions options) {
+    if (options.target != null) {
+      throw InvalidGenerationSourceError(
+        'ForyStruct.target is valid only on an external serializer '
+        'declaration class.',
+        element: element,
+      );
+    }
+    if (!options.exposePrivateFields) {
       throw InvalidGenerationSourceError(
-        'ForyStruct.constructor is valid only when ForyStruct.target is set.',
+        '${element.displayName} does not own a concrete generated serializer. '
+        'Annotate it with ForyStruct(exposePrivateFields: true) only when it '
+        'is a public hierarchy boundary that exposes private state.',
         element: element,
       );
     }
+    if (!options.evolving) {
+      throw InvalidGenerationSourceError(
+        'ForyStruct.evolving has no meaning on provider-only declaration '
+        '${element.displayName}. Remove evolving: false.',
+        element: element,
+      );
+    }
+    if (options.constructorName != null) {
+      throw InvalidGenerationSourceError(
+        'ForyStruct.constructor has no meaning on provider-only declaration '
+        '${element.displayName}.',
+        element: element,
+      );
+    }
+  }
+
+  _GeneratedStructSpec _analyzeExternalStruct(
+    ClassElement element,
+    _StructOptions options,
+    DartType annotatedTarget,
+  ) {
+    final constructorName = options.constructorName;
     final declarationFields = element.fields
         .where(_isStoredInstanceField)
         .toList(growable: false);
 
-    final targetType =
-        annotatedTarget == null
-            ? element.thisType
-            : _validateExternalTarget(element, annotatedTarget);
-    if (annotatedTarget != null) {
-      _validateExternalDeclaration(element, targetType, declarationFields);
-    }
-    final targetTypeLiteral =
-        annotatedTarget == null
-            ? _typeReferenceLiteral(targetType)
-            : _externalTargetTypeLiteral(targetType, element);
+    final targetType = _validateExternalTarget(element, annotatedTarget);
+    _validateExternalDeclaration(element, targetType, declarationFields);
+    final targetTypeLiteral = _externalTargetTypeLiteral(targetType, element);
     final selectedConstructor = _selectConstructor(
       element,
       targetType,
       targetTypeLiteral,
       constructorName,
-      external: annotatedTarget != null,
+      external: true,
     );
 
-    final fields = declarationFields
-        .where((field) => !_isIgnored(field))
-        .map(
-          (field) =>
-              _analyzeField(element, targetType, targetTypeLiteral, field),
-        )
-        .toList(growable: false);
-    final graphFieldCount =
-        annotatedTarget == null
-            ? declarationFields.length
-            : _externalGraphFieldCount(element, targetType, declarationFields);
-
-    final sortedFields = _sortFields(fields);
-    final constructionModel = _buildConstructionModel(
+    final indexedCodegenNames = _hasCodegenNameConflict(
+      declarationFields
+          .where((field) => !_isIgnored(field))
+          .map((field) => field.displayName),
+    );
+    final fields = <_GeneratedFieldSpec>[];
+    for (var index = 0; index < declarationFields.length; index += 1) {
+      final field = declarationFields[index];
+      if (_isIgnored(field)) {
+        continue;
+      }
+      fields.add(
+        _analyzeField(
+          element,
+          targetType,
+          targetTypeLiteral,
+          field,
+          indexedCodegenNames ? 'field$index' : field.displayName,
+        ),
+      );
+    }
+    final analyzedConstruction = _buildExternalConstructionModel(
       element,
-      targetType,
       targetTypeLiteral,
       selectedConstructor,
       constructorName,
-      sortedFields,
+      fields,
+    );
+    _validateConstructorSelfReference(
+      declaration: element,
+      targetType: targetType,
+      targetTypeLiteral: targetTypeLiteral,
+      fields: fields,
+      constructionModel: analyzedConstruction,
     );
+    _validateWireIdentities(element, fields);
+    final sortedFields = _sortFields(fields);
     return _GeneratedStructSpec(
       name: element.displayName,
       targetType: targetType,
       targetTypeLiteral: targetTypeLiteral,
-      evolving: evolving,
+      evolving: options.evolving,
       fields: sortedFields,
-      graphFieldCount: graphFieldCount,
-      constructionModel: constructionModel,
+      storageFieldCount: _externalGraphFieldCount(
+        element,
+        targetType,
+        declarationFields,
+      ),
+      constructionModel: analyzedConstruction,
     );
   }
 
+  Future<_GeneratedStructSpec> _analyzeOrdinaryStruct(
+    ClassElement element,
+    _StructOptions options,
+    _OrdinaryConstructorAnalyzer constructorAnalyzer,
+    BuildStep buildStep,
+  ) async {
+    if (options.constructorName != null) {
+      throw InvalidGenerationSourceError(
+        'ForyStruct.constructor is valid only when ForyStruct.target is set. '
+        'Ordinary structs use their unnamed generative constructor.',
+        element: element,
+      );
+    }
+    final targetType = element.thisType;
+    final targetTypeLiteral = _typeReferenceLiteral(targetType);
+    final selectedConstructor = _selectConstructor(
+      element,
+      targetType,
+      targetTypeLiteral,
+      null,
+      external: false,
+    );
+    final hierarchy = _discoverHierarchyStorage(targetType, element);
+    final discovered = hierarchy.fields;
+    final included = <_DiscoveredField>[];
+    final includedNames = <String>[];
+    for (final field in discovered) {
+      if (_isIgnored(field.declaration)) {
+        continue;
+      }
+      included.add(field);
+      includedNames.add(field.declaration.displayName);
+    }
+    final indexedCodegenNames = _hasCodegenNameConflict(includedNames);
+    final fields = <_GeneratedFieldSpec>[];
+    for (final field in included) {
+      final fieldLibrary = field.declaration.library;
+      if (field.declaration.isPrivate &&
+          fieldLibrary != element.library &&
+          fieldLibrary.uri.scheme == 'file' &&
+          _canonicalLibraryUris?.containsKey(fieldLibrary) != true) {
+        try {
+          final assetId = await buildStep.resolver.assetIdForElement(
+            fieldLibrary,
+          );
+          final canonicalUris =
+              _canonicalLibraryUris ??= Map<LibraryElement, String>.identity();
+          canonicalUris[fieldLibrary] = assetId.uri.toString();
+        } on Object catch (error) {
+          throw InvalidGenerationSourceError(
+            'Fory cannot derive a stable private-field companion identity '
+            'from library ${fieldLibrary.uri}: $error',
+            element: field.declaration,
+          );
+        }
+      }
+      final name = field.declaration.displayName;
+      fields.add(
+        _analyzeOrdinaryField(
+          element,
+          targetType,
+          targetTypeLiteral,
+          hierarchy,
+          field,
+          indexedCodegenNames ? 'field${field.storageIndex}' : name,
+        ),
+      );
+    }
+    final analyzedConstruction = await constructorAnalyzer.build(
+      declaration: element,
+      targetTypeLiteral: targetTypeLiteral,
+      constructor: selectedConstructor,
+      fields: fields,
+    );
+    _validateConstructorSelfReference(
+      declaration: element,
+      targetType: targetType,
+      targetTypeLiteral: targetTypeLiteral,
+      fields: fields,
+      constructionModel: analyzedConstruction,
+    );
+    _validateWireIdentities(element, fields);
+    final sortedFields = _sortFields(fields);
+    return _GeneratedStructSpec(
+      name: element.displayName,
+      targetType: targetType,
+      targetTypeLiteral: targetTypeLiteral,
+      evolving: options.evolving,
+      fields: sortedFields,
+      storageFieldCount: hierarchy.fields.length,
+      constructionModel: analyzedConstruction,
+    );
+  }
+
+  bool _hasCodegenNameConflict(Iterable<String> names) {
+    final sourceNames = <String>{};
+    final capitalizedNames = <String>{};
+    for (final name in names) {
+      if (!sourceNames.add(name)) {
+        return true;
+      }
+      final capitalized = '${name[0].toUpperCase()}${name.substring(1)}';
+      if (!capitalizedNames.add(capitalized)) {
+        return true;
+      }
+    }
+    return false;
+  }
+
   InterfaceType _validateExternalTarget(
     ClassElement declaration,
     DartType annotatedTarget,
@@ -422,7 +682,7 @@ final class ForyGenerator extends Generator {
                 ? ''
                 : '<${alias.typeArguments.map(render).join(', ')}>';
         final nullable =
-            alias.nullabilitySuffix == NullabilitySuffix.question ? '?' : '';
+            type.nullabilitySuffix == NullabilitySuffix.question ? '?' : '';
         return '$base$arguments$nullable';
       }
       if (type is DynamicType) {
@@ -465,8 +725,8 @@ final class ForyGenerator extends Generator {
         typeElement.library?.isDartCore == true) {
       return typeElement.displayName;
     }
-    if (_importPrefixByElement.containsKey(typeElement)) {
-      final prefix = _importPrefixByElement[typeElement];
+    if (_hasImportReference(typeElement)) {
+      final prefix = _importPrefixFor(typeElement);
       return prefix == null
           ? typeElement.displayName
           : '$prefix.${typeElement.displayName}';
@@ -479,29 +739,1108 @@ final class ForyGenerator extends Generator {
     );
   }
 
-  void _buildImportPrefixMap(LibraryReader library) {
-    _importPrefixByElement.clear();
-    for (final import in library.element.firstFragment.libraryImports) {
-      final importedLibrary = import.importedLibrary;
-      final prefix = import.prefix?.element.displayName;
-      if (importedLibrary == null) {
+  bool _hasImportReference(Element element) {
+    final baseElement = element.baseElement;
+    if (!_resolvedImportElements.add(baseElement)) {
+      return _importPrefixByElement.containsKey(baseElement);
+    }
+    final name = element.displayName;
+    final sourceFragment = _sourceLibrary.firstFragment;
+    final unprefixed = sourceFragment.scope.lookup(name).getter;
+    if (unprefixed != null && identical(unprefixed.baseElement, baseElement)) {
+      _importPrefixByElement[baseElement] = null;
+      return true;
+    }
+
+    String? selectedPrefix;
+    for (final import in sourceFragment.libraryImports) {
+      if (import.prefix?.isDeferred == true) {
+        continue;
+      }
+      final prefix = import.prefix?.element;
+      if (prefix == null || prefix.displayName.isEmpty) {
+        continue;
+      }
+      final importedElement = import.namespace.definedNames2[name];
+      if (importedElement == null ||
+          !identical(importedElement.baseElement, baseElement)) {
+        continue;
+      }
+      final resolved = prefix.scope.lookup(name).getter;
+      if (resolved == null || !identical(resolved.baseElement, baseElement)) {
+        continue;
+      }
+      final prefixName = prefix.displayName;
+      if (selectedPrefix == null || prefixName.compareTo(selectedPrefix) < 0) {
+        selectedPrefix = prefixName;
+      }
+    }
+    if (selectedPrefix == null) {
+      return false;
+    }
+    _importPrefixByElement[baseElement] = selectedPrefix;
+    return true;
+  }
+
+  String? _importPrefixFor(Element element) {
+    final library = element.library;
+    if (library == _sourceLibrary || library?.isDartCore == true) {
+      return null;
+    }
+    _hasImportReference(element);
+    return _importPrefixByElement[element.baseElement];
+  }
+
+  _GeneratedEnumSpec _analyzeEnum(EnumElement element) {
+    return _GeneratedEnumSpec(
+      name: element.displayName,
+      usesRawValue: _enumUsesRawValueElement(element),
+    );
+  }
+
+  _HierarchyStorage _discoverHierarchyStorage(
+    InterfaceType targetType,
+    Element errorElement,
+  ) {
+    final cached = _hierarchyStorageByType[targetType];
+    if (cached != null) {
+      return cached;
+    }
+    final layers = <_HierarchyLayer>[];
+    final fields = <_DiscoveredField>[];
+    final visiting = <InterfaceElement>{};
+
+    void appendLayer(InterfaceType type) {
+      if (type.element is ClassElement &&
+          (type.element as ClassElement).isDartCoreObject) {
+        return;
+      }
+      final layer = _HierarchyLayer(type: type, index: layers.length);
+      layers.add(layer);
+      for (final declaration in type.element.fields) {
+        if (!_isStoredInstanceField(declaration)) {
+          continue;
+        }
+        fields.add(
+          _DiscoveredField(
+            declaration: declaration.baseElement,
+            layer: layer,
+            storageIndex: fields.length,
+          ),
+        );
+      }
+    }
+
+    void visitClass(InterfaceType type) {
+      final element = type.element;
+      if (element is ClassElement && element.isDartCoreObject) {
+        return;
+      }
+      if (!visiting.add(element)) {
+        throw InvalidGenerationSourceError(
+          'Fory cannot flatten a cyclic superclass or mixin storage chain for '
+          '${targetType.getDisplayString()}.',
+          element: errorElement,
+        );
+      }
+      final superclass = type.superclass;
+      if (superclass != null) {
+        visitClass(superclass);
+      }
+      for (final mixin in type.mixins) {
+        appendLayer(mixin);
+      }
+      appendLayer(type);
+      visiting.remove(element);
+    }
+
+    if (targetType.element is MixinElement) {
+      appendLayer(targetType);
+    } else {
+      visitClass(targetType);
+    }
+    final hierarchy = _HierarchyStorage(layers: layers, fields: fields);
+    _hierarchyStorageByType[targetType] = hierarchy;
+    return hierarchy;
+  }
+
+  _GeneratedFieldSpec _analyzeOrdinaryField(
+    ClassElement declaration,
+    InterfaceType targetType,
+    String targetTypeLiteral,
+    _HierarchyStorage hierarchy,
+    _DiscoveredField discovered,
+    String codegenName,
+  ) {
+    final field = discovered.declaration;
+    final effectiveType = _effectiveFieldType(discovered);
+    _validateOrdinaryFieldType(effectiveType, field, targetTypeLiteral);
+    _validateTypeVisibleFrom(
+      effectiveType,
+      declaration.library,
+      errorElement: field,
+      context:
+          'Storage field ${field.enclosingElement.displayName}.'
+          '${field.displayName}',
+    );
+    final access = _resolveOrdinaryFieldAccess(
+      declaration,
+      targetType,
+      hierarchy,
+      discovered,
+      effectiveType,
+    );
+    return _createGeneratedFieldSpec(
+      annotationField: field,
+      effectiveType: effectiveType,
+      codegenName: codegenName,
+      writable: !field.isFinal,
+      access: access,
+    );
+  }
+
+  void _validateOrdinaryFieldType(
+    DartType type,
+    FieldElement field,
+    String targetTypeLiteral,
+  ) {
+    void validate(DartType current) {
+      if (current is DynamicType) {
+        return;
+      }
+      if (current is InvalidType || current is TypeParameterType) {
+        throw InvalidGenerationSourceError(
+          'Storage field ${field.enclosingElement.displayName}.'
+          '${field.displayName} has unresolved effective type '
+          '${type.getDisplayString()} while generating $targetTypeLiteral.',
+          element: field,
+        );
+      }
+      if (current is FunctionType ||
+          current is RecordType ||
+          current is VoidType ||
+          current is NeverType ||
+          current.isDartCoreFunction ||
+          current.isDartCoreRecord ||
+          current.isDartCoreNull) {
+        throw InvalidGenerationSourceError(
+          'Storage field ${field.enclosingElement.displayName}.'
+          '${field.displayName} has unsupported effective type '
+          '${type.getDisplayString()}. Mark the field with '
+          '@ForyField(ignore: true), use a supported serializable type, or '
+          'use a manual serializer.',
+          element: field,
+        );
+      }
+      if (current is InterfaceType) {
+        for (final argument in current.typeArguments) {
+          validate(argument);
+        }
+        return;
+      }
+      throw InvalidGenerationSourceError(
+        'Storage field ${field.enclosingElement.displayName}.'
+        '${field.displayName} has unsupported effective type '
+        '${type.getDisplayString()}. Mark the field with '
+        '@ForyField(ignore: true), use a supported serializable type, or use '
+        'a manual serializer.',
+        element: field,
+      );
+    }
+
+    validate(type);
+  }
+
+  DartType _effectiveFieldType(_DiscoveredField discovered) {
+    final declaration = discovered.declaration;
+    final getter = discovered.layer.type.getGetter(declaration.displayName);
+    final variable = getter?.variable;
+    if (getter == null ||
+        variable is! FieldElement ||
+        !identical(variable.baseElement, declaration.baseElement)) {
+      throw InvalidGenerationSourceError(
+        'Fory could not substitute storage field '
+        '${discovered.layer.type.element.displayName}.'
+        '${declaration.displayName} in its instantiated hierarchy layer.',
+        element: declaration,
+      );
+    }
+    return getter.returnType;
+  }
+
+  _FieldAccessPlan _resolveOrdinaryFieldAccess(
+    ClassElement child,
+    InterfaceType targetType,
+    _HierarchyStorage hierarchy,
+    _DiscoveredField discovered,
+    DartType effectiveType,
+  ) {
+    final field = discovered.declaration;
+    final sameLibrary = field.library == child.library;
+    if (field.isPublic || sameLibrary) {
+      _validateExactFieldAccess(
+        targetType: targetType,
+        lookupLibrary: child.library,
+        discovered: discovered,
+        effectiveType: effectiveType,
+        hierarchy: hierarchy,
+        requireSetter: !field.isFinal,
+        child: child,
+      );
+      return _FieldAccessPlan.direct(field.displayName);
+    }
+
+    final boundary = _resolvePrivateAccessBoundary(
+      child: child,
+      hierarchy: hierarchy,
+      discovered: discovered,
+    );
+    _validateExactFieldAccess(
+      targetType: targetType,
+      lookupLibrary: field.library,
+      discovered: discovered,
+      effectiveType: effectiveType,
+      hierarchy: hierarchy,
+      requireSetter: !field.isFinal,
+      child: child,
+    );
+    final identity = _privateFieldIdentity(field);
+    final digest = _privateFieldDigest(identity);
+    return _FieldAccessPlan.companion(
+      fieldName: field.displayName,
+      companion: boundary,
+      getter: '\$g$digest',
+      setter: field.isFinal ? null : '\$s$digest',
+    );
+  }
+
+  String _resolvePrivateAccessBoundary({
+    required ClassElement child,
+    required _HierarchyStorage hierarchy,
+    required _DiscoveredField discovered,
+  }) {
+    final field = discovered.declaration;
+    final authorizedHelpers = <String>{};
+    for (final layer in hierarchy.layers.reversed) {
+      if (layer.index < discovered.layer.index) {
+        continue;
+      }
+      final boundary = layer.type.element;
+      if (boundary.library != field.library ||
+          boundary.isPrivate ||
+          !_foryStructChecker.hasAnnotationOf(boundary)) {
         continue;
       }
+      final options = _structOptions(boundary);
+      if (!options.exposePrivateFields || options.target != null) {
+        continue;
+      }
+      final boundaryHierarchy = _discoverHierarchyStorage(
+        boundary.thisType,
+        boundary,
+      );
+      var carriesField = false;
+      for (final candidate in boundaryHierarchy.fields) {
+        if (identical(candidate.declaration.baseElement, field.baseElement)) {
+          carriesField = true;
+          break;
+        }
+      }
+      if (!carriesField) {
+        continue;
+      }
+      final helperName = _privateAccessHelperName(boundary);
+      authorizedHelpers.add(helperName);
+      final reference = _companionReferenceFromChild(
+        child.library,
+        boundary,
+        helperName,
+      );
+      if (reference != null) {
+        return reference;
+      }
+    }
+
+    final helperNames = authorizedHelpers.toList()..sort();
+    final authorization =
+        helperNames.isNotEmpty
+            ? 'An authorized public boundary exists, but its generated '
+                'companion is not visible through the child library imports '
+                'and re-exports. Expected ${helperNames.join(' or ')}.'
+            : 'No public hierarchy boundary in the declaring library is '
+                'annotated with '
+                '@ForyStruct(exposePrivateFields: true).';
+    final consumerPermission =
+        _structOptions(child).exposePrivateFields
+            ? ' The annotation on ${child.displayName} authorizes only '
+                'private storage declared by ${child.library.uri}; it cannot '
+                'authorize this field.'
+            : '';
+    throw InvalidGenerationSourceError(
+      'Fory discovered inherited private field '
+      '${field.enclosingElement.displayName}.${field.displayName}, declared '
+      'by ${field.library.uri}, while generating ${child.displayName} in '
+      '${child.library.uri}. $authorization$consumerPermission Import a '
+      'qualifying boundary together with its generated companion, mark the '
+      'field with @ForyField(ignore: true), or use a manual serializer.',
+      element: field,
+    );
+  }
+
+  String? _companionReferenceFromChild(
+    LibraryElement childLibrary,
+    InterfaceElement boundary,
+    String helperName,
+  ) {
+    Element? localCollision = childLibrary.getClass(helperName);
+    localCollision ??= childLibrary.getMixin(helperName);
+    localCollision ??= childLibrary.getEnum(helperName);
+    localCollision ??= childLibrary.getExtension(helperName);
+    localCollision ??= childLibrary.getExtensionType(helperName);
+    localCollision ??= childLibrary.getTypeAlias(helperName);
+    localCollision ??= childLibrary.getTopLevelFunction(helperName);
+    localCollision ??= childLibrary.getTopLevelVariable(helperName);
+    localCollision ??= childLibrary.getGetter(helperName);
+    localCollision ??= childLibrary.getSetter(helperName);
+
+    final validPrefixes = <String?>{};
+    final ambiguousPrefixes = <String?>{};
+    var blockedByLocalCollision = false;
+    final importsByPrefix = <String?, List<LibraryImport>>{};
+    for (final import in childLibrary.firstFragment.libraryImports) {
+      final prefix = import.prefix?.element.displayName;
       final normalizedPrefix = prefix == null || prefix.isEmpty ? null : prefix;
-      for (final importedElement in import.namespace.definedNames2.values) {
-        if (!_importPrefixByElement.containsKey(importedElement) ||
-            normalizedPrefix == null) {
-          _importPrefixByElement[importedElement] = normalizedPrefix;
+      if (normalizedPrefix == helperName) {
+        localCollision ??= import.prefix!.element;
+      }
+      if (import.prefix?.isDeferred == true) {
+        continue;
+      }
+      var imports = importsByPrefix[normalizedPrefix];
+      if (imports == null) {
+        imports = <LibraryImport>[];
+        importsByPrefix[normalizedPrefix] = imports;
+      }
+      imports.add(import);
+    }
+    for (final MapEntry(key: prefix, value: imports)
+        in importsByPrefix.entries) {
+      final resolvedBoundary =
+          prefix == null
+              ? childLibrary.firstFragment.scope
+                  .lookup(boundary.displayName)
+                  .getter
+              : imports.first.prefix!.element.scope
+                  .lookup(boundary.displayName)
+                  .getter;
+      if (resolvedBoundary == null ||
+          !identical(resolvedBoundary.baseElement, boundary.baseElement)) {
+        continue;
+      }
+      final helperOwners = <LibraryElement>{};
+      for (final import in imports) {
+        final importedLibrary = import.importedLibrary;
+        if (importedLibrary == null) {
+          continue;
+        }
+        if (!_combinatorsAllow(import.combinators, helperName)) {
+          continue;
+        }
+        helperOwners.addAll(
+          _companionOwners(importedLibrary, helperName, <LibraryElement>{}),
+        );
+      }
+      if (prefix == null &&
+          localCollision != null &&
+          localCollision.library != boundary.library &&
+          helperOwners.contains(boundary.library)) {
+        blockedByLocalCollision = true;
+        continue;
+      }
+      if (helperOwners.contains(boundary.library) && helperOwners.length > 1) {
+        ambiguousPrefixes.add(prefix);
+        continue;
+      }
+      if (helperOwners.length == 1 && helperOwners.single == boundary.library) {
+        validPrefixes.add(prefix);
+      }
+    }
+    if (validPrefixes.isEmpty) {
+      if (blockedByLocalCollision) {
+        throw InvalidGenerationSourceError(
+          'Generated companion name $helperName from ${boundary.library.uri} '
+          'collides with ${localCollision!.displayName} in '
+          '${childLibrary.uri}.',
+          element: boundary,
+        );
+      }
+      if (ambiguousPrefixes.isNotEmpty) {
+        final names = <String>[];
+        for (final prefix in ambiguousPrefixes) {
+          names.add(prefix ?? '<unprefixed>');
+        }
+        names.sort();
+        throw InvalidGenerationSourceError(
+          'Generated companion $helperName is ambiguous in import namespace '
+          '${names.join(', ')}.',
+          element: boundary,
+        );
+      }
+      return null;
+    }
+    if (validPrefixes.contains(null)) {
+      return helperName;
+    }
+    String? prefix;
+    for (final candidate in validPrefixes) {
+      if (candidate != null &&
+          (prefix == null || candidate.compareTo(prefix) < 0)) {
+        prefix = candidate;
+      }
+    }
+    return prefix == null ? helperName : '$prefix.$helperName';
+  }
+
+  Set<LibraryElement> _companionOwners(
+    LibraryElement current,
+    String name,
+    Set<LibraryElement> visiting,
+  ) {
+    if (!visiting.add(current)) {
+      return const <LibraryElement>{};
+    }
+    final owners = <LibraryElement>{};
+    if (_generatesCompanion(current, name)) {
+      visiting.remove(current);
+      return <LibraryElement>{current};
+    }
+    final existing = current.publicNamespace.definedNames2[name];
+    final existingLibrary = existing?.library;
+    if (existingLibrary == current) {
+      visiting.remove(current);
+      return <LibraryElement>{current};
+    }
+    if (existingLibrary != null) {
+      owners.add(existingLibrary);
+    }
+    for (final export in current.firstFragment.libraryExports) {
+      final exported = export.exportedLibrary;
+      if (exported == null || !_combinatorsAllow(export.combinators, name)) {
+        continue;
+      }
+      owners.addAll(_companionOwners(exported, name, visiting));
+    }
+    visiting.remove(current);
+    return owners;
+  }
+
+  bool _generatesCompanion(LibraryElement library, String helperName) {
+    const suffix = 'ForyFieldAccess';
+    if (!helperName.startsWith(r'$') || !helperName.endsWith(suffix)) {
+      return false;
+    }
+    final boundaryName = helperName.substring(
+      1,
+      helperName.length - suffix.length,
+    );
+    final boundary =
+        library.getClass(boundaryName) ?? library.getMixin(boundaryName);
+    if (boundary == null ||
+        boundary.isPrivate ||
+        !_foryStructChecker.hasAnnotationOf(boundary)) {
+      return false;
+    }
+    final options = _structOptions(boundary);
+    return options.target == null && options.exposePrivateFields;
+  }
+
+  bool _combinatorsAllow(List<NamespaceCombinator> combinators, String name) {
+    var visible = true;
+    for (final combinator in combinators) {
+      if (combinator is ShowElementCombinator) {
+        visible = visible && combinator.shownNames.contains(name);
+      } else if (combinator is HideElementCombinator) {
+        visible = visible && !combinator.hiddenNames.contains(name);
+      }
+    }
+    return visible;
+  }
+
+  Future<_PrivateAccessCompanionSpec> _analyzePrivateAccessCompanion(
+    InterfaceElement boundary,
+    BuildStep buildStep,
+  ) async {
+    if (boundary.isPrivate) {
+      throw InvalidGenerationSourceError(
+        'ForyStruct.exposePrivateFields requires a public hierarchy boundary; '
+        '${boundary.displayName} is private.',
+        element: boundary,
+      );
+    }
+    if (boundary is ClassElement &&
+        !boundary.isExtendableOutside &&
+        !boundary.isMixableOutside) {
+      throw InvalidGenerationSourceError(
+        '${boundary.displayName} cannot be extended or mixed in outside '
+        '${boundary.library.uri}, so it cannot expose inherited private '
+        'state to consumer subclasses.',
+        element: boundary,
+      );
+    }
+    final boundaryType = boundary.thisType;
+    final hierarchy = _discoverHierarchyStorage(boundaryType, boundary);
+    final fields = <_DiscoveredField>[];
+    for (final field in hierarchy.fields) {
+      if (field.declaration.isPrivate &&
+          field.declaration.library == boundary.library &&
+          !_isIgnored(field.declaration)) {
+        fields.add(field);
+      }
+    }
+    if (fields.isEmpty) {
+      throw InvalidGenerationSourceError(
+        'ForyStruct.exposePrivateFields on ${boundary.displayName} has no '
+        'non-ignored private storage owned by ${boundary.library.uri} to '
+        'expose.',
+        element: boundary,
+      );
+    }
+
+    final helperName = _privateAccessHelperName(boundary);
+    final existing = boundary.library.publicNamespace.definedNames2[helperName];
+    if (existing != null) {
+      final existingAsset = await buildStep.resolver.assetIdForElement(
+        existing,
+      );
+      if (!buildStep.allowedOutputs.contains(existingAsset)) {
+        throw InvalidGenerationSourceError(
+          'Generated companion name $helperName collides with an existing '
+          'top-level declaration in ${boundary.library.uri}.',
+          element: boundary,
+        );
+      }
+    }
+
+    final typeParameters = boundary.typeParameters.toSet();
+    final receiverType = _companionBoundaryType(boundary);
+    final methodTypeParameters = _companionMethodTypeParameters(boundary);
+    if (boundary.library != _sourceLibrary) {
+      throw StateError(
+        'Private-field companion boundary ${boundary.displayName} is not '
+        'owned by the current input library.',
+      );
+    }
+    if (boundary.library.uri.scheme == 'file') {
+      final canonicalUris =
+          _canonicalLibraryUris ??= Map<LibraryElement, String>.identity();
+      canonicalUris[boundary.library] = buildStep.inputId.uri.toString();
+    }
+    final methods = <_PrivateAccessMethodSpec>[];
+    final identitiesByDigest = <String, String>{};
+    for (final discovered in fields) {
+      final field = discovered.declaration;
+      final effectiveType = _effectiveFieldType(discovered);
+      _validateExactFieldAccess(
+        targetType: boundaryType,
+        lookupLibrary: boundary.library,
+        discovered: discovered,
+        effectiveType: effectiveType,
+        hierarchy: hierarchy,
+        requireSetter: !field.isFinal,
+        child: boundary,
+      );
+      final identity = _privateFieldIdentity(field);
+      final digest = _privateFieldDigest(identity);
+      final previous = identitiesByDigest[digest];
+      if (previous != null && previous != identity) {
+        throw InvalidGenerationSourceError(
+          'Private-field access digest collision between $previous and '
+          '$identity.',
+          element: field,
+        );
+      }
+      identitiesByDigest[digest] = identity;
+      methods.add(
+        _PrivateAccessMethodSpec(
+          fieldName: field.displayName,
+          fieldType: _companionTypeCode(
+            effectiveType,
+            boundary,
+            allowedTypeParameters: typeParameters,
+          ),
+          getterName: '\$g$digest',
+          setterName: field.isFinal ? null : '\$s$digest',
+        ),
+      );
+    }
+    methods.sort((left, right) => left.getterName.compareTo(right.getterName));
+    return _PrivateAccessCompanionSpec(
+      name: helperName,
+      receiverType: receiverType,
+      methodTypeParameters: methodTypeParameters,
+      methods: methods,
+    );
+  }
+
+  String _companionBoundaryType(InterfaceElement boundary) {
+    if (boundary.typeParameters.isEmpty) {
+      return boundary.displayName;
+    }
+    final arguments = <String>[];
+    for (final parameter in boundary.typeParameters) {
+      arguments.add(parameter.displayName);
+    }
+    return '${boundary.displayName}<${arguments.join(', ')}>';
+  }
+
+  String _companionMethodTypeParameters(InterfaceElement boundary) {
+    if (boundary.typeParameters.isEmpty) {
+      return '';
+    }
+    final allowed = boundary.typeParameters.toSet();
+    final parameters = <String>[];
+    for (final parameter in boundary.typeParameters) {
+      final bound = parameter.bound;
+      parameters.add(
+        bound == null
+            ? parameter.displayName
+            : '${parameter.displayName} extends '
+                '${_companionTypeCode(bound, boundary, allowedTypeParameters: allowed)}',
+      );
+    }
+    return '<${parameters.join(', ')}>';
+  }
+
+  String _companionTypeCode(
+    DartType type,
+    InterfaceElement boundary, {
+    required Set<TypeParameterElement> allowedTypeParameters,
+    bool requireNamespace = true,
+    bool useAlias = true,
+  }) {
+    if (type is DynamicType ||
+        type is FunctionType ||
+        type is InvalidType ||
+        type.isDartCoreFunction) {
+      throw InvalidGenerationSourceError(
+        'Private-field companion for ${boundary.displayName} cannot expose '
+        '${type.getDisplayString()}. Companion signatures must be exact, '
+        'public, and non-callback.',
+        element: boundary,
+      );
+    }
+    if (type is RecordType || type.isDartCoreRecord || type.isDartCoreNull) {
+      throw InvalidGenerationSourceError(
+        'Private-field companion for ${boundary.displayName} cannot expose '
+        '${type.getDisplayString()} because ordinary ForyStruct fields do '
+        'not support that type.',
+        element: boundary,
+      );
+    }
+    final nullable =
+        type.nullabilitySuffix == NullabilitySuffix.question ? '?' : '';
+    final alias = type.alias;
+    if (useAlias && alias != null && alias.element.isPublic) {
+      final base = _companionTypeName(
+        alias.element,
+        boundary,
+        requireNamespace: requireNamespace,
+      );
+      final arguments = _companionTypeArguments(
+        alias.typeArguments,
+        boundary,
+        allowedTypeParameters: allowedTypeParameters,
+        requireNamespace: requireNamespace,
+        useAlias: true,
+      );
+      // A public alias supplies the emitted name, but it cannot conceal a
+      // private nominal type or callback in its underlying signature.
+      _companionTypeCode(
+        type,
+        boundary,
+        allowedTypeParameters: allowedTypeParameters,
+        requireNamespace: false,
+        useAlias: false,
+      );
+      return arguments.isEmpty
+          ? '$base$nullable'
+          : '$base<$arguments>$nullable';
+    }
+    if (type is InterfaceType) {
+      final element = type.element;
+      final base = _companionTypeName(
+        element,
+        boundary,
+        requireNamespace: requireNamespace,
+      );
+      final arguments = _companionTypeArguments(
+        type.typeArguments,
+        boundary,
+        allowedTypeParameters: allowedTypeParameters,
+        requireNamespace: requireNamespace,
+        useAlias: useAlias,
+      );
+      return arguments.isEmpty
+          ? '$base$nullable'
+          : '$base<$arguments>$nullable';
+    }
+    if (type is TypeParameterType) {
+      if (!allowedTypeParameters.contains(type.element)) {
+        throw InvalidGenerationSourceError(
+          'Private-field companion for ${boundary.displayName} contains '
+          'unbound type parameter ${type.element.displayName}.',
+          element: boundary,
+        );
+      }
+      return '${type.element.displayName}$nullable';
+    }
+    throw InvalidGenerationSourceError(
+      'Private-field companion for ${boundary.displayName} cannot render '
+      '${type.getDisplayString()} as an exact public Dart type.',
+      element: boundary,
+    );
+  }
+
+  String _companionTypeArguments(
+    List<DartType> arguments,
+    InterfaceElement boundary, {
+    required Set<TypeParameterElement> allowedTypeParameters,
+    required bool requireNamespace,
+    required bool useAlias,
+  }) {
+    if (arguments.isEmpty) {
+      return '';
+    }
+    final rendered = <String>[];
+    for (final argument in arguments) {
+      rendered.add(
+        _companionTypeCode(
+          argument,
+          boundary,
+          allowedTypeParameters: allowedTypeParameters,
+          requireNamespace: requireNamespace,
+          useAlias: useAlias,
+        ),
+      );
+    }
+    return rendered.join(', ');
+  }
+
+  String _companionTypeName(
+    Element element,
+    InterfaceElement boundary, {
+    required bool requireNamespace,
+  }) {
+    if (element.isPrivate) {
+      throw InvalidGenerationSourceError(
+        'Private-field companion for ${boundary.displayName} would expose '
+        'private nominal type ${element.displayName}.',
+        element: boundary,
+      );
+    }
+    final library = element.library;
+    if (library != null &&
+        library != boundary.library &&
+        !library.isDartCore &&
+        requireNamespace &&
+        !_hasImportReference(element)) {
+      throw InvalidGenerationSourceError(
+        'Private-field companion for ${boundary.displayName} uses public type '
+        '${element.displayName}, but that exact type is not nameable from '
+        '${boundary.library.uri}.',
+        element: boundary,
+      );
+    }
+    if (element.library == boundary.library ||
+        element.library?.isDartCore == true) {
+      return element.displayName;
+    }
+    final prefix = _importPrefixFor(element);
+    return prefix == null
+        ? element.displayName
+        : '$prefix.${element.displayName}';
+  }
+
+  String _privateAccessHelperName(InterfaceElement boundary) =>
+      '\$${boundary.displayName}ForyFieldAccess';
+
+  String _privateFieldIdentity(FieldElement field) =>
+      '${_canonicalLibraryUri(field.library)}\u0000'
+      '${field.enclosingElement.displayName}\u0000${field.displayName}';
+
+  String _privateFieldDigest(String identity) {
+    var hash = BigInt.parse('cbf29ce484222325', radix: 16);
+    final prime = BigInt.parse('100000001b3', radix: 16);
+    final mask = (BigInt.one << 64) - BigInt.one;
+    for (final byte in utf8.encode(identity)) {
+      hash = ((hash ^ BigInt.from(byte)) * prime) & mask;
+    }
+    return hash.toRadixString(16).padLeft(16, '0');
+  }
+
+  String _canonicalLibraryUri(LibraryElement library) {
+    final uri = library.uri;
+    if (uri.scheme != 'file') {
+      return uri.toString();
+    }
+    final canonical = _canonicalLibraryUris?[library];
+    if (canonical == null) {
+      throw StateError(
+        'Canonical asset URI was not prepared for ${library.uri}.',
+      );
+    }
+    return canonical;
+  }
+
+  void _writePrivateAccessCompanion(
+    StringBuffer output,
+    _PrivateAccessCompanionSpec companion,
+  ) {
+    output
+      ..writeln('/// @nodoc')
+      ..writeln('abstract final class ${companion.name} {');
+    for (final method in companion.methods) {
+      output
+        ..writeln("  @pragma('vm:prefer-inline')")
+        ..writeln(
+          '  static ${method.fieldType} ${method.getterName}'
+          '${companion.methodTypeParameters}('
+          '${companion.receiverType} value) => value.${method.fieldName};',
+        )
+        ..writeln();
+      final setterName = method.setterName;
+      if (setterName != null) {
+        output
+          ..writeln("  @pragma('vm:prefer-inline')")
+          ..writeln(
+            '  static void $setterName${companion.methodTypeParameters}(',
+          )
+          ..writeln('    ${companion.receiverType} value,')
+          ..writeln('    ${method.fieldType} fieldValue,')
+          ..writeln('  ) {')
+          ..writeln('    value.${method.fieldName} = fieldValue;')
+          ..writeln('  }')
+          ..writeln();
+      }
+    }
+    output
+      ..writeln('}')
+      ..writeln();
+  }
+
+  void _validateExactFieldAccess({
+    required InterfaceType targetType,
+    required LibraryElement lookupLibrary,
+    required _DiscoveredField discovered,
+    required DartType effectiveType,
+    required _HierarchyStorage hierarchy,
+    required bool requireSetter,
+    required InterfaceElement child,
+  }) {
+    final field = discovered.declaration;
+    _DiscoveredField? laterApplication;
+    for (
+      var index = discovered.storageIndex + 1;
+      index < hierarchy.fields.length;
+      index += 1
+    ) {
+      final candidate = hierarchy.fields[index];
+      if (identical(candidate.declaration.baseElement, field.baseElement)) {
+        laterApplication = candidate;
+        break;
+      }
+    }
+    if (laterApplication != null) {
+      throw InvalidGenerationSourceError(
+        'Fory cannot address storage field '
+        '${field.enclosingElement.displayName}.${field.displayName} in '
+        '${child.displayName}: the same field declaration is applied more '
+        'than once and this slot is hidden by a later application.',
+        element: field,
+      );
+    }
+
+    final getter = targetType.lookUpGetter(
+      field.displayName,
+      lookupLibrary,
+      concrete: true,
+    );
+    final getterVariable = getter?.variable;
+    if (getter == null ||
+        getterVariable is! FieldElement ||
+        !identical(getterVariable.baseElement, field.baseElement) ||
+        getter.returnType != effectiveType) {
+      throw InvalidGenerationSourceError(
+        'Fory discovered storage field '
+        '${field.enclosingElement.displayName}.${field.displayName}, but '
+        '${child.displayName} does not expose that exact slot through its '
+        'effective getter. A later field or accessor hides the storage.',
+        element: field,
+      );
+    }
+    if (!requireSetter) {
+      return;
+    }
+    final setter = targetType.lookUpSetter(
+      field.displayName,
+      lookupLibrary,
+      concrete: true,
+    );
+    final setterVariable = setter?.variable;
+    final parameters = setter?.formalParameters;
+    if (setter == null ||
+        setterVariable is! FieldElement ||
+        !identical(setterVariable.baseElement, field.baseElement) ||
+        parameters == null ||
+        parameters.length != 1 ||
+        parameters.single.type != effectiveType) {
+      throw InvalidGenerationSourceError(
+        'Mutable storage field ${field.enclosingElement.displayName}.'
+        '${field.displayName} on ${child.displayName} does not expose an '
+        'exact setter for ${_typeCodeString(effectiveType)}.',
+        element: field,
+      );
+    }
+  }
+
+  void _validateTypeVisibleFrom(
+    DartType type,
+    LibraryElement library, {
+    required Element errorElement,
+    required String context,
+  }) {
+    void validateElement(Element element, {required bool requireNamespace}) {
+      final elementLibrary = element.library;
+      if (elementLibrary == null ||
+          elementLibrary == library ||
+          elementLibrary.isDartCore) {
+        return;
+      }
+      if (element.isPrivate ||
+          (requireNamespace && !_hasImportReference(element))) {
+        throw InvalidGenerationSourceError(
+          '$context uses type ${element.displayName}, which is not nameable '
+          'from ${library.uri}. Import the exact public type or ignore the '
+          'field explicitly.',
+          element: errorElement,
+        );
+      }
+    }
+
+    void validate(
+      DartType current, {
+      bool requireNamespace = true,
+      bool useAlias = true,
+    }) {
+      if (current is InvalidType) {
+        throw InvalidGenerationSourceError(
+          '$context has an invalid or unresolved Dart type.',
+          element: errorElement,
+        );
+      }
+      final alias = current.alias;
+      if (useAlias && alias != null) {
+        final aliasElement = alias.element;
+        final aliasVisible =
+            aliasElement.library == library ||
+            (aliasElement.isPublic && _hasImportReference(aliasElement));
+        if (aliasVisible) {
+          validateElement(aliasElement, requireNamespace: requireNamespace);
+          for (final argument in alias.typeArguments) {
+            validate(argument);
+          }
+          // The visible alias is sufficient for generated source. Continue
+          // only to reject private nominal types hidden under that alias.
+          validate(current, requireNamespace: false, useAlias: false);
+          return;
+        }
+      }
+      if (current is InterfaceType) {
+        validateElement(current.element, requireNamespace: requireNamespace);
+        for (final argument in current.typeArguments) {
+          validate(
+            argument,
+            requireNamespace: requireNamespace,
+            useAlias: useAlias,
+          );
+        }
+        return;
+      }
+      if (current is TypeParameterType) {
+        throw InvalidGenerationSourceError(
+          '$context contains unresolved type parameter '
+          '${current.element.displayName}.',
+          element: errorElement,
+        );
+      }
+      if (current is FunctionType) {
+        validate(
+          current.returnType,
+          requireNamespace: requireNamespace,
+          useAlias: useAlias,
+        );
+        for (final parameter in current.formalParameters) {
+          validate(
+            parameter.type,
+            requireNamespace: requireNamespace,
+            useAlias: useAlias,
+          );
+        }
+        return;
+      }
+      if (current is RecordType) {
+        for (final field in current.positionalFields) {
+          validate(
+            field.type,
+            requireNamespace: requireNamespace,
+            useAlias: useAlias,
+          );
+        }
+        for (final field in current.namedFields) {
+          validate(
+            field.type,
+            requireNamespace: requireNamespace,
+            useAlias: useAlias,
+          );
         }
       }
     }
+
+    validate(type);
   }
 
-  _GeneratedEnumSpec _analyzeEnum(EnumElement element) {
-    return _GeneratedEnumSpec(
-      name: element.displayName,
-      usesRawValue: _enumUsesRawValueElement(element),
-    );
+  void _validateWireIdentities(
+    ClassElement declaration,
+    List<_GeneratedFieldSpec> fields,
+  ) {
+    final ids = <int, _GeneratedFieldSpec>{};
+    final names = <String, _GeneratedFieldSpec>{};
+    for (final field in fields) {
+      final id = field.id;
+      if (id != null) {
+        final previous = ids[id];
+        if (previous != null) {
+          throw InvalidGenerationSourceError(
+            'Fory struct ${declaration.displayName} has duplicate field id '
+            '$id on ${previous.name} and ${field.name}.',
+            element: field.declaration,
+          );
+        }
+        ids[id] = field;
+      } else {
+        final wireName = field.wireName!;
+        final previous = names[wireName];
+        if (previous != null) {
+          throw InvalidGenerationSourceError(
+            'Fory struct ${declaration.displayName} has duplicate canonical '
+            'field name $wireName on ${previous.name} and ${field.name}. '
+            'Assign distinct numeric ids or ignore one field explicitly.',
+            element: field.declaration,
+          );
+        }
+        names[wireName] = field;
+      }
+    }
   }
 
   _GeneratedFieldSpec _analyzeField(
@@ -509,31 +1848,8 @@ final class ForyGenerator extends Generator {
     InterfaceType targetType,
     String targetTypeLiteral,
     FieldElement field,
+    String codegenName,
   ) {
-    final annotation = _fieldAnnotationOf(field);
-    final reader = annotation == null ? null : ConstantReader(annotation);
-    final idValue = reader?.peek('id');
-    final nullableValue = reader?.peek('nullable');
-    final dynamicValue = reader?.peek('dynamic');
-    final rawFieldId =
-        idValue == null || idValue.isNull ? null : idValue.intValue;
-    if (rawFieldId != null && rawFieldId < 0) {
-      throw InvalidGenerationSourceError(
-        'Fory field id must be non-negative.',
-        element: field,
-      );
-    }
-    final fieldId = rawFieldId;
-    final nullable =
-        nullableValue == null || nullableValue.isNull
-            ? _isNullable(field.type)
-            : nullableValue.boolValue;
-    final dynamic =
-        dynamicValue == null || dynamicValue.isNull
-            ? _autoDynamic(field.type)
-            : dynamicValue.boolValue;
-    final ref = reader?.peek('ref')?.boolValue ?? false;
-    final typeSpec = _analyzeTypeSpecAnnotation(field, reader);
     final getter = targetType.lookUpGetter(
       field.displayName,
       declaration.library,
@@ -580,24 +1896,63 @@ final class ForyGenerator extends Generator {
       }
     }
 
-    return _GeneratedFieldSpec(
-      name: field.displayName,
-      type: field.type,
-      displayType: _typeCodeString(field.type),
-      identifier:
-          fieldId != null ? '$fieldId' : _toSnakeCase(field.displayName),
-      id: fieldId,
-      nullable: nullable,
-      ref: ref,
-      dynamic: dynamic,
+    return _createGeneratedFieldSpec(
+      annotationField: field,
+      effectiveType: field.type,
+      codegenName: codegenName,
       writable: setter != null,
+      access: _FieldAccessPlan.direct(field.displayName),
+    );
+  }
+
+  _GeneratedFieldSpec _createGeneratedFieldSpec({
+    required FieldElement annotationField,
+    required DartType effectiveType,
+    required String codegenName,
+    required bool writable,
+    required _FieldAccessPlan access,
+  }) {
+    final annotation = _fieldAnnotationOf(annotationField);
+    final reader = annotation == null ? null : ConstantReader(annotation);
+    final idValue = reader?.peek('id');
+    final nullableValue = reader?.peek('nullable');
+    final dynamicValue = reader?.peek('dynamic');
+    final rawFieldId =
+        idValue == null || idValue.isNull ? null : idValue.intValue;
+    if (rawFieldId != null && rawFieldId < 0) {
+      throw InvalidGenerationSourceError(
+        'Fory field id must be non-negative.',
+        element: annotationField,
+      );
+    }
+    final nullable =
+        nullableValue == null || nullableValue.isNull
+            ? _isNullable(effectiveType)
+            : nullableValue.boolValue;
+    final dynamic =
+        dynamicValue == null || dynamicValue.isNull
+            ? _autoDynamic(effectiveType)
+            : dynamicValue.boolValue;
+    final ref = reader?.peek('ref')?.boolValue ?? false;
+    final typeSpec = _analyzeTypeSpecAnnotation(annotationField, reader);
+    return _GeneratedFieldSpec(
+      name: annotationField.displayName,
+      type: effectiveType,
+      displayType: _typeCodeString(effectiveType),
+      wireName:
+          rawFieldId == null ? _toSnakeCase(annotationField.displayName) : null,
+      id: rawFieldId,
+      writable: writable,
+      codegenName: codegenName,
+      declaration: annotationField.baseElement,
+      access: access,
       fieldType: _fieldTypeForType(
-        field.type,
+        effectiveType,
         nullable: nullable,
         ref: ref,
         dynamic: dynamic,
         typeSpec: typeSpec,
-        errorElement: field,
+        errorElement: annotationField,
       ),
     );
   }
@@ -740,9 +2095,8 @@ final class ForyGenerator extends Generator {
     );
   }
 
-  _ConstructionModel _buildConstructionModel(
+  _ConstructionModel _buildExternalConstructionModel(
     ClassElement declaration,
-    InterfaceType targetType,
     String targetTypeLiteral,
     ConstructorElement constructor,
     String? constructorName,
@@ -759,7 +2113,7 @@ final class ForyGenerator extends Generator {
       for (final field in fields) field.name: field,
     };
     final arguments = <_ConstructorArgumentSpec>[];
-    final constructorFieldNames = <String>{};
+    final constructorFields = <_GeneratedFieldSpec>{};
     var omittedOptionalPositional = false;
     for (final parameter in constructor.formalParameters) {
       final parameterName = parameter.displayName;
@@ -801,10 +2155,10 @@ final class ForyGenerator extends Generator {
           element: declaration,
         );
       }
-      constructorFieldNames.add(field.name);
+      constructorFields.add(field);
       arguments.add(
         _ConstructorArgumentSpec(
-          fieldName: field.name,
+          field: field,
           parameterName: parameterName,
           named: parameter.isNamed,
         ),
@@ -812,7 +2166,7 @@ final class ForyGenerator extends Generator {
     }
 
     for (final field in fields) {
-      if (!constructorFieldNames.contains(field.name) && !field.writable) {
+      if (!constructorFields.contains(field) && !field.writable) {
         throw InvalidGenerationSourceError(
           'Field ${declaration.displayName}.${field.name} is not consumed by '
           '${_constructorReference(targetTypeLiteral, constructorName)} and '
@@ -824,38 +2178,50 @@ final class ForyGenerator extends Generator {
       }
     }
 
-    // Constructor arguments are read before the target can be published, so a
-    // tracked path through carrier metadata cannot resolve back to this target.
-    final selfRefField =
-        fields
-            .where(
-              (field) => _hasTrackedTargetReference(
-                field.type,
-                field.fieldType,
-                targetType,
-              ),
-            )
-            .firstOrNull;
-    if (selfRefField != null) {
-      throw InvalidGenerationSourceError(
-        'Constructor-based generated serializers cannot bind '
-        'reference-tracked self paths before construction. '
-        'Use a writable zero-required-argument generative constructor for '
-        '$targetTypeLiteral or a manual serializer. Offending field: '
-        '${declaration.displayName}.${selfRefField.name}.',
-        element: declaration,
-      );
+    final postConstructionFields = Set<_GeneratedFieldSpec>.identity();
+    for (final field in fields) {
+      if (!constructorFields.contains(field)) {
+        postConstructionFields.add(field);
+      }
     }
 
-    final postConstructionFields = fields
-        .where((field) => !constructorFieldNames.contains(field.name))
-        .map((field) => field.name)
-        .toList(growable: false);
-
     return _ConstructionModel.constructor(
       constructorName: constructorName,
       arguments: arguments,
-      postConstructionFieldNames: postConstructionFields,
+      postConstructionFields: postConstructionFields,
+    );
+  }
+
+  void _validateConstructorSelfReference({
+    required ClassElement declaration,
+    required InterfaceType targetType,
+    required String targetTypeLiteral,
+    required List<_GeneratedFieldSpec> fields,
+    required _ConstructionModel constructionModel,
+  }) {
+    if (constructionModel.mode != _ConstructorMode.constructor) {
+      return;
+    }
+    // Constructor arguments are read before the target can be published, so a
+    // tracked path through carrier metadata cannot resolve back to this target.
+    _GeneratedFieldSpec? selfRefField;
+    for (final field in fields) {
+      if (_hasTrackedTargetReference(field.type, field.fieldType, targetType)) {
+        selfRefField = field;
+        break;
+      }
+    }
+    if (selfRefField == null) {
+      return;
+    }
+    final fieldOwner = selfRefField.declaration.enclosingElement.displayName;
+    throw InvalidGenerationSourceError(
+      'Constructor-based generated serializers cannot bind '
+      'reference-tracked self paths before construction. Use a writable '
+      'zero-parameter generative constructor for $targetTypeLiteral '
+      'or a manual serializer. Offending field: '
+      '$fieldOwner.${selfRefField.name}.',
+      element: declaration,
     );
   }
 
@@ -1006,23 +2372,23 @@ final class ForyGenerator extends Generator {
           field,
           directPrimitiveRunStartByIndex[index]!,
           index,
-          'value.${field.name}',
+          field.access.read('value'),
           '      ',
         );
       } else if (_usesDirectGeneratedBasicFastPath(field)) {
         output.writeln(
-          '      ${_directGeneratedWriteStatement(field, 'value.${field.name}')};',
+          '      ${_directGeneratedWriteStatement(field, field.access.read('value'))};',
         );
       } else if (_usesDirectGeneratedTypedContainerWriteFastPath(field)) {
         output.writeln(
-          '      ${_directGeneratedTypedContainerWriteStatement(field, index, 'value.${field.name}')};',
+          '      ${_directGeneratedTypedContainerWriteStatement(field, index, field.access.read('value'))};',
         );
       } else {
         _writeGeneratedDescriptorValue(
           output,
           field,
           index,
-          'value.${field.name}',
+          field.access.read('value'),
           '      ',
         );
       }
@@ -1074,36 +2440,15 @@ final class ForyGenerator extends Generator {
               '      ',
             );
           }
-          if (_usesReservedGeneratedFastPath(field)) {
-            _writeDirectGeneratedBufferReadStatement(
-              output,
-              field,
-              directPrimitiveRunStartByIndex[index]!,
-              index,
-              'value.${field.name}',
-              '      ',
-            );
-          } else if (_usesDirectGeneratedBasicFastPath(field)) {
-            output.writeln(
-              '      value.${field.name} = ${_directGeneratedReadExpression(field)};',
-            );
-          } else if (_usesDirectGeneratedTypedContainerReadFastPath(field)) {
-            output.writeln(
-              '      value.${field.name} = ${_directGeneratedTypedContainerReadExpression(structSpec.name, field, 'fields[$index]')};',
-            );
-          } else if (_usesDirectGeneratedStructFieldFastPath(field)) {
-            output.writeln(
-              '      value.${field.name} = $readerFunctionName(readGeneratedStructDirectValue(context, fields[$index]), value.${field.name});',
-            );
-          } else if (_usesDirectGeneratedDeclaredReadFastPath(field)) {
-            output.writeln(
-              '      value.${field.name} = $readerFunctionName(readGeneratedStructDeclaredValue(context, fields[$index]), value.${field.name});',
-            );
-          } else {
-            output.writeln(
-              '      value.${field.name} = $readerFunctionName(readGeneratedStructDescriptorValue(context, fields[$index], value.${field.name}), value.${field.name});',
-            );
-          }
+          _writeMutableFieldRead(
+            output,
+            structSpec,
+            field,
+            index,
+            directPrimitiveRunStartByIndex[index],
+            readerFunctionName,
+            '      ',
+          );
           final directPrimitiveEndRun = directPrimitiveRunByEnd[index];
           if (directPrimitiveEndRun != null) {
             _writeDirectGeneratedReadRunEnd(
@@ -1175,12 +2520,14 @@ final class ForyGenerator extends Generator {
         final constructorInvocation = _constructorInvocation(structSpec);
         output.writeln('      context.reserveGraphMemory($graphObjectBytes);');
         output.writeln('      final value = $constructorInvocation;');
-        for (final fieldName
-            in structSpec.constructionModel.postConstructionFieldNames) {
-          final field = structSpec.fields.firstWhere(
-            (item) => item.name == fieldName,
-          );
-          output.writeln('      value.${field.name} = ${field.localName};');
+        for (final field in structSpec.fields) {
+          if (structSpec.constructionModel.postConstructionFields.contains(
+            field,
+          )) {
+            output.writeln(
+              '      ${field.access.write('value', field.localName)};',
+            );
+          }
         }
         output.writeln('    return value;');
     }
@@ -1213,6 +2560,59 @@ final class ForyGenerator extends Generator {
     }
   }
 
+  void _writeMutableFieldRead(
+    StringBuffer output,
+    _GeneratedStructSpec structSpec,
+    _GeneratedFieldSpec field,
+    int index,
+    int? primitiveRunStart,
+    String readerFunctionName,
+    String indent,
+  ) {
+    final currentValue = field.access.read('value');
+    if (_usesReservedGeneratedFastPath(field)) {
+      final target =
+          field.access.isDirect
+              ? currentValue
+              : 'final ${field.displayType} ${field.localName}';
+      _writeDirectGeneratedBufferReadStatement(
+        output,
+        field,
+        primitiveRunStart!,
+        index,
+        target,
+        indent,
+      );
+      if (!field.access.isDirect) {
+        output.writeln(
+          '$indent${field.access.write('value', field.localName)};',
+        );
+      }
+      return;
+    }
+
+    late final String decoded;
+    if (_usesDirectGeneratedBasicFastPath(field)) {
+      decoded = _directGeneratedReadExpression(field);
+    } else if (_usesDirectGeneratedTypedContainerReadFastPath(field)) {
+      decoded = _directGeneratedTypedContainerReadExpression(
+        structSpec.name,
+        field,
+        'fields[$index]',
+      );
+    } else if (_usesDirectGeneratedStructFieldFastPath(field)) {
+      decoded =
+          '$readerFunctionName(readGeneratedStructDirectValue(context, fields[$index]), $currentValue)';
+    } else if (_usesDirectGeneratedDeclaredReadFastPath(field)) {
+      decoded =
+          '$readerFunctionName(readGeneratedStructDeclaredValue(context, fields[$index]), $currentValue)';
+    } else {
+      decoded =
+          '$readerFunctionName(readGeneratedStructDescriptorValue(context, fields[$index], $currentValue), $currentValue)';
+    }
+    output.writeln('$indent${field.access.write('value', decoded)};');
+  }
+
   void _writeCompatibleStructReadMethod(
     StringBuffer output,
     _GeneratedStructSpec structSpec, {
@@ -1325,25 +2725,21 @@ final class ForyGenerator extends Generator {
     for (var index = 0; index < structSpec.fields.length; index += 1) {
       final field = structSpec.fields[index];
       output.writeln('        case ${index * 2}:');
-      _writeExactCompatRead(
+      _writeMutableExactCompatRead(
         output,
         structSpec,
         field,
         index,
-        'value.${field.name}',
-        'value.${field.name}',
         '          ',
       );
       output
         ..writeln('          break;')
         ..writeln('        case ${index * 2 + 1}:');
-      _writeCompatConversionRead(
+      _writeMutableCompatConversionRead(
         output,
         structSpec,
         field,
         index,
-        'value.${field.name}',
-        'value.${field.name}',
         'field',
         '          ',
       );
@@ -1359,6 +2755,75 @@ final class ForyGenerator extends Generator {
       ..writeln('    return value;');
   }
 
+  void _writeMutableExactCompatRead(
+    StringBuffer output,
+    _GeneratedStructSpec structSpec,
+    _GeneratedFieldSpec field,
+    int index,
+    String indent,
+  ) {
+    final currentValue = field.access.read('value');
+    if (field.access.isDirect) {
+      _writeExactCompatRead(
+        output,
+        structSpec,
+        field,
+        index,
+        currentValue,
+        currentValue,
+        indent,
+      );
+      return;
+    }
+    final decoded = '${field.localName}Exact';
+    _writeExactCompatRead(
+      output,
+      structSpec,
+      field,
+      index,
+      'final ${field.displayType} $decoded',
+      currentValue,
+      indent,
+    );
+    output.writeln('$indent${field.access.write('value', decoded)};');
+  }
+
+  void _writeMutableCompatConversionRead(
+    StringBuffer output,
+    _GeneratedStructSpec structSpec,
+    _GeneratedFieldSpec field,
+    int index,
+    String readField,
+    String indent,
+  ) {
+    final currentValue = field.access.read('value');
+    if (field.access.isDirect) {
+      _writeCompatConversionRead(
+        output,
+        structSpec,
+        field,
+        index,
+        currentValue,
+        currentValue,
+        readField,
+        indent,
+      );
+      return;
+    }
+    final decoded = '${field.localName}Converted';
+    _writeCompatConversionRead(
+      output,
+      structSpec,
+      field,
+      index,
+      'final ${field.displayType} $decoded',
+      currentValue,
+      readField,
+      indent,
+    );
+    output.writeln('$indent${field.access.write('value', decoded)};');
+  }
+
   void _writeCtorCompatSwitch(
     StringBuffer output,
     _GeneratedStructSpec structSpec,
@@ -1428,12 +2893,10 @@ final class ForyGenerator extends Generator {
       '    context.reserveGraphMemory(${_graphObjectBytes(structSpec)});',
     );
     output.writeln('    final value = $constructorInvocation;');
-    for (final fieldName
-        in structSpec.constructionModel.postConstructionFieldNames) {
-      final field = structSpec.fields.firstWhere(
-        (item) => item.name == fieldName,
-      );
-      output.writeln('    value.${field.name} = ${field.localName};');
+    for (final field in structSpec.fields) {
+      if (structSpec.constructionModel.postConstructionFields.contains(field)) {
+        output.writeln('    ${field.access.write('value', field.localName)};');
+      }
     }
     output.writeln('    return value;');
   }
@@ -1630,9 +3093,7 @@ final class ForyGenerator extends Generator {
     final positionalArguments = <String>[];
     final namedArguments = <String>[];
     for (final argument in structSpec.constructionModel.arguments) {
-      final field = structSpec.fields.firstWhere(
-        (item) => item.name == argument.fieldName,
-      );
+      final field = argument.field;
       if (argument.named) {
         namedArguments.add('${argument.parameterName}: ${field.localName}');
       } else {
@@ -1647,7 +3108,7 @@ final class ForyGenerator extends Generator {
   }
 
   int _graphObjectBytes(_GeneratedStructSpec structSpec) =>
-      _structObjectOwnerBytes + structSpec.graphFieldCount * _referenceBytes;
+      _structObjectOwnerBytes + structSpec.storageFieldCount * _referenceBytes;
 
   int _externalGraphFieldCount(
     ClassElement declaration,
@@ -1717,12 +3178,12 @@ final class ForyGenerator extends Generator {
   }
 
   String _fieldInfoLiteral(_GeneratedFieldSpec field) {
-    final idLiteral = field.id == null ? 'null' : '${field.id}';
+    final identifier = field.id?.toString() ?? field.wireName!;
     return '''
   GeneratedFieldInfo(
     name: '${field.name}',
-    identifier: '${field.identifier}',
-    id: $idLiteral,
+    identifier: '$identifier',
+    id: ${field.id},
     fieldType: ${_fieldTypeLiteral(field.fieldType)},
   ),''';
   }
@@ -2060,8 +3521,14 @@ GeneratedFieldType(
 
   bool _isGeneratedStructType(DartType type) {
     final element = _withoutNullability(type).element;
-    return element is ClassElement &&
-        _foryStructChecker.hasAnnotationOf(element);
+    if (element is! ClassElement ||
+        !_foryStructChecker.hasAnnotationOf(element)) {
+      return false;
+    }
+    final options = _structOptions(element);
+    return options.target == null &&
+        _ownsOrdinarySerializer(element) &&
+        element.typeParameters.isEmpty;
   }
 
   bool _usesDirectGeneratedTypedContainerWriteFastPath(
@@ -3120,29 +4587,17 @@ GeneratedFieldType(
   String _containerElementReaderFunctionName(
     String structName,
     _GeneratedFieldSpec field,
-  ) {
-    final fieldName =
-        '${field.name[0].toUpperCase()}${field.name.substring(1)}';
-    return '_read$structName${fieldName}Element';
-  }
+  ) => '_read$structName${field.capitalizedCodegenName}Element';
 
   String _containerKeyReaderFunctionName(
     String structName,
     _GeneratedFieldSpec field,
-  ) {
-    final fieldName =
-        '${field.name[0].toUpperCase()}${field.name.substring(1)}';
-    return '_read$structName${fieldName}Key';
-  }
+  ) => '_read$structName${field.capitalizedCodegenName}Key';
 
   String _containerValueReaderFunctionName(
     String structName,
     _GeneratedFieldSpec field,
-  ) {
-    final fieldName =
-        '${field.name[0].toUpperCase()}${field.name.substring(1)}';
-    return '_read$structName${fieldName}Value';
-  }
+  ) => '_read$structName${field.capitalizedCodegenName}Value';
 
   List<_GeneratedFieldSpec> _sortFields(List<_GeneratedFieldSpec> fields) {
     final primitiveFields = <_GeneratedFieldSpec>[];
@@ -3151,7 +4606,7 @@ GeneratedFieldType(
 
     for (final field in fields) {
       if (_isPrimitiveTypeId(field.fieldType.typeId)) {
-        if (field.nullable) {
+        if (field.fieldType.nullable) {
           boxedPrimitiveFields.add(field);
         } else {
           primitiveFields.add(field);
@@ -3195,7 +4650,7 @@ GeneratedFieldType(
     if (keyCompare != 0) {
       return keyCompare;
     }
-    return left.name.compareTo(right.name);
+    return 0;
   }
 
   int _compareOtherFields(_GeneratedFieldSpec left, _GeneratedFieldSpec right) {
@@ -3203,7 +4658,7 @@ GeneratedFieldType(
     if (keyCompare != 0) {
       return keyCompare;
     }
-    return left.name.compareTo(right.name);
+    return 0;
   }
 
   int _compareFieldIdentity(
@@ -3224,7 +4679,7 @@ GeneratedFieldType(
     if ((leftId == null || leftId < 0) && rightId != null && rightId >= 0) {
       return 1;
     }
-    final keyCompare = left.identifier.compareTo(right.identifier);
+    final keyCompare = left.wireName!.compareTo(right.wireName!);
     if (keyCompare != 0) {
       return keyCompare;
     }
@@ -4185,9 +5640,11 @@ GeneratedFieldType(
     // Rebuilding a nullable interface type drops its alias, so preserve the
     // visible alias before removing nullability from the underlying type.
     final alias = type.alias;
-    if (alias != null) {
+    if (alias != null &&
+        (alias.element.library == _sourceLibrary ||
+            (alias.element.isPublic && _hasImportReference(alias.element)))) {
       final aliasElement = alias.element;
-      final prefix = _importPrefixByElement[aliasElement];
+      final prefix = _importPrefixFor(aliasElement);
       final elementName = aliasElement.displayName;
       final baseName = prefix == null ? elementName : '$prefix.$elementName';
       if (alias.typeArguments.isEmpty) {
@@ -4199,7 +5656,7 @@ GeneratedFieldType(
     final nonNullable = _withoutNullability(type);
     if (nonNullable is InterfaceType) {
       final element = nonNullable.element;
-      final prefix = _importPrefixByElement[element];
+      final prefix = _importPrefixFor(element);
       final elementName = element.displayName;
       final baseName = prefix == null ? elementName : '$prefix.$elementName';
       if (nonNullable.typeArguments.isEmpty) {
@@ -4297,13 +5754,81 @@ final class _GeneratedEnumSpec {
   const _GeneratedEnumSpec({required this.name, required this.usesRawValue});
 }
 
+final class _StructOptions {
+  final bool evolving;
+  final DartType? target;
+  final String? constructorName;
+  final bool exposePrivateFields;
+
+  const _StructOptions({
+    required this.evolving,
+    required this.target,
+    required this.constructorName,
+    required this.exposePrivateFields,
+  });
+}
+
+final class _HierarchyStorage {
+  final List<_HierarchyLayer> layers;
+  final List<_DiscoveredField> fields;
+
+  const _HierarchyStorage({required this.layers, required this.fields});
+}
+
+final class _HierarchyLayer {
+  final InterfaceType type;
+  final int index;
+
+  const _HierarchyLayer({required this.type, required this.index});
+}
+
+final class _DiscoveredField {
+  final FieldElement declaration;
+  final _HierarchyLayer layer;
+  final int storageIndex;
+
+  const _DiscoveredField({
+    required this.declaration,
+    required this.layer,
+    required this.storageIndex,
+  });
+}
+
+final class _PrivateAccessCompanionSpec {
+  final String name;
+  final String receiverType;
+  final String methodTypeParameters;
+  final List<_PrivateAccessMethodSpec> methods;
+
+  const _PrivateAccessCompanionSpec({
+    required this.name,
+    required this.receiverType,
+    required this.methodTypeParameters,
+    required this.methods,
+  });
+}
+
+final class _PrivateAccessMethodSpec {
+  final String fieldName;
+  final String fieldType;
+  final String getterName;
+  final String? setterName;
+
+  const _PrivateAccessMethodSpec({
+    required this.fieldName,
+    required this.fieldType,
+    required this.getterName,
+    required this.setterName,
+  });
+}
+
 final class _GeneratedStructSpec {
   final String name;
   final InterfaceType targetType;
   final String targetTypeLiteral;
   final bool evolving;
   final List<_GeneratedFieldSpec> fields;
-  final int graphFieldCount;
+  final int storageFieldCount;
   final _ConstructionModel constructionModel;
 
   const _GeneratedStructSpec({
@@ -4312,7 +5837,7 @@ final class _GeneratedStructSpec {
     required this.targetTypeLiteral,
     required this.evolving,
     required this.fields,
-    required this.graphFieldCount,
+    required this.storageFieldCount,
     required this.constructionModel,
   });
 }
@@ -4327,32 +5852,71 @@ final class _GeneratedFieldSpec {
   final String name;
   final DartType type;
   final String displayType;
-  final String identifier;
+  final String? wireName;
   final int? id;
-  final bool nullable;
-  final bool ref;
-  final bool? dynamic;
   final bool writable;
+  final String codegenName;
+  final FieldElement declaration;
+  final _FieldAccessPlan access;
   final _GeneratedFieldTypeSpec fieldType;
 
   const _GeneratedFieldSpec({
     required this.name,
     required this.type,
     required this.displayType,
-    required this.identifier,
+    required this.wireName,
     required this.id,
-    required this.nullable,
-    required this.ref,
-    required this.dynamic,
     required this.writable,
+    required this.codegenName,
+    required this.declaration,
+    required this.access,
     required this.fieldType,
   });
+
   String readerFunctionName(String structName) {
-    final fieldName = '${name[0].toUpperCase()}${name.substring(1)}';
-    return '_read$structName$fieldName';
+    return '_read$structName$capitalizedCodegenName';
   }
 
-  String get localName => '_${name}Value';
+  String get capitalizedCodegenName =>
+      '${codegenName[0].toUpperCase()}${codegenName.substring(1)}';
+
+  String get localName => '_${codegenName}Value';
+}
+
+final class _FieldAccessPlan {
+  final String fieldName;
+  final String? companion;
+  final String? getter;
+  final String? setter;
+
+  const _FieldAccessPlan.direct(this.fieldName)
+    : companion = null,
+      getter = null,
+      setter = null;
+
+  const _FieldAccessPlan.companion({
+    required this.fieldName,
+    required this.companion,
+    required this.getter,
+    required this.setter,
+  });
+
+  bool get isDirect => companion == null;
+
+  String read(String receiver) {
+    final companion = this.companion;
+    return companion == null
+        ? '$receiver.$fieldName'
+        : '$companion.${getter!}($receiver)';
+  }
+
+  String write(String receiver, String value) {
+    final companion = this.companion;
+    if (companion == null) {
+      return '$receiver.$fieldName = $value';
+    }
+    return '$companion.${setter!}($receiver, $value)';
+  }
 }
 
 final class _DirectGeneratedPrimitiveRun {
@@ -4389,27 +5953,27 @@ final class _ConstructionModel {
   final _ConstructorMode mode;
   final String? constructorName;
   final List<_ConstructorArgumentSpec> arguments;
-  final List<String> postConstructionFieldNames;
+  final Set<_GeneratedFieldSpec> postConstructionFields;
 
   const _ConstructionModel.mutable({required this.constructorName})
     : mode = _ConstructorMode.mutable,
       arguments = const <_ConstructorArgumentSpec>[],
-      postConstructionFieldNames = const <String>[];
+      postConstructionFields = const <_GeneratedFieldSpec>{};
 
   const _ConstructionModel.constructor({
     required this.constructorName,
     required this.arguments,
-    required this.postConstructionFieldNames,
+    required this.postConstructionFields,
   }) : mode = _ConstructorMode.constructor;
 }
 
 final class _ConstructorArgumentSpec {
-  final String fieldName;
+  final _GeneratedFieldSpec field;
   final String parameterName;
   final bool named;
 
   const _ConstructorArgumentSpec({
-    required this.fieldName,
+    required this.field,
     required this.parameterName,
     required this.named,
   });
diff --git a/dart/packages/fory/lib/src/codegen/generated_support.dart b/dart/packages/fory/lib/src/codegen/generated_support.dart
index fd0c14d03..f15155d67 100644
--- a/dart/packages/fory/lib/src/codegen/generated_support.dart
+++ b/dart/packages/fory/lib/src/codegen/generated_support.dart
@@ -134,8 +134,6 @@ final class GeneratedStructFieldDescriptor {
     required this.usesDeclaredType,
   });
 
-  String get name => field.name;
-
   meta_types.FieldType get fieldType => field.fieldType;
 }
 
diff --git a/dart/packages/fory/lib/src/meta/field_info.dart b/dart/packages/fory/lib/src/meta/field_info.dart
index b3b525f3e..b52ee63e3 100644
--- a/dart/packages/fory/lib/src/meta/field_info.dart
+++ b/dart/packages/fory/lib/src/meta/field_info.dart
@@ -20,9 +20,21 @@
 import 'package:fory/src/meta/field_type.dart';
 
 final class FieldInfo {
+  /// The local source name used in diagnostics.
+  ///
+  /// Remote fields use their textual wire identity because no Dart source
+  /// declaration exists.
   final String name;
+
+  /// The protocol's textual id-or-name form used for encoding and hashing.
+  ///
+  /// Compatible matching uses [id] for tagged fields and this value only for
+  /// untagged fields, so the two identity domains remain distinct.
   final String identifier;
+
+  /// The numeric wire tag for a tagged field.
   final int? id;
+
   final FieldType fieldType;
 
   const FieldInfo({
diff --git a/dart/packages/fory/lib/src/resolver/type_resolver.dart b/dart/packages/fory/lib/src/resolver/type_resolver.dart
index 23a99c0fe..798ef7386 100644
--- a/dart/packages/fory/lib/src/resolver/type_resolver.dart
+++ b/dart/packages/fory/lib/src/resolver/type_resolver.dart
@@ -219,31 +219,48 @@ bool _fieldTypeUsesNestedTypeDefinitions(FieldType fieldType) {
   return false;
 }
 
-void _validateFieldInfo(FieldInfo field) {
-  final id = field.id;
-  if (id != null && id < 0) {
-    throw ArgumentError('Field id for ${field.name} must be non-negative.');
-  }
-}
-
-List<FieldInfo> _validateFieldInfos(Iterable<FieldInfo> fields) {
-  final validated = <FieldInfo>[];
-  final usedIds = <int, String>{};
+String? _fieldIdentityError(List<FieldInfo> fields) {
+  final fieldsById = <int, FieldInfo>{};
+  final fieldsByName = <String, FieldInfo>{};
   for (final field in fields) {
-    _validateFieldInfo(field);
     final id = field.id;
     if (id != null) {
-      final existing = usedIds[id];
-      if (existing != null && existing != field.name) {
-        throw ArgumentError(
-          'Duplicate field id $id for fields $existing and ${field.name}.',
-        );
+      if (id < 0) {
+        return 'Field id $id must be non-negative.';
       }
-      usedIds[id] = field.name;
+      if (field.identifier != id.toString()) {
+        return 'Tagged field ${field.name} has textual identifier '
+            '${field.identifier}, which must match field id $id.';
+      }
+      final existing = fieldsById[id];
+      if (existing != null) {
+        return 'Duplicate field id $id for fields ${existing.name} and '
+            '${field.name}.';
+      }
+      fieldsById[id] = field;
+      continue;
     }
-    validated.add(field);
+    final identifier = field.identifier;
+    if (identifier.isEmpty) {
+      return 'Field wire name must not be empty.';
+    }
+    final existing = fieldsByName[identifier];
+    if (existing != null) {
+      return 'Duplicate field wire name $identifier for fields '
+          '${existing.name} and ${field.name}.';
+    }
+    fieldsByName[identifier] = field;
+  }
+  return null;
+}
+
+List<FieldInfo> _validateLocalFieldInfos(List<FieldInfo> fields) {
+  final validated = List<FieldInfo>.unmodifiable(fields);
+  final error = _fieldIdentityError(validated);
+  if (error != null) {
+    throw ArgumentError(error);
   }
-  return List<FieldInfo>.unmodifiable(validated);
+  return validated;
 }
 
 final class TypeResolver {
@@ -352,7 +369,7 @@ final class TypeResolver {
         resolvedTypeName == null ? null : typeNameMetaString(resolvedTypeName);
     final normalizedFields =
         registrationKind == RegistrationKind.struct
-            ? _validateFieldInfos(fields)
+            ? _validateLocalFieldInfos(fields)
             : const <FieldInfo>[];
     final resolved = TypeInfo(
       type: type,
@@ -1247,6 +1264,12 @@ final class TypeResolver {
       throw StateError('Invalid TypeDef metadata size.');
     }
     header.validateBodyHash(metaBody);
+    if (isStruct) {
+      final error = _fieldIdentityError(fields);
+      if (error != null) {
+        throw StateError('Invalid remote TypeDef: $error');
+      }
+    }
     final resolved =
         userTypeId != null
             ? resolveUserById(userTypeId)
diff --git a/dart/packages/fory/lib/src/serializer/scalar_conversion.dart b/dart/packages/fory/lib/src/serializer/scalar_conversion.dart
index d4caca3b2..14b065259 100644
--- a/dart/packages/fory/lib/src/serializer/scalar_conversion.dart
+++ b/dart/packages/fory/lib/src/serializer/scalar_conversion.dart
@@ -101,23 +101,32 @@ CompatibleScalarConversion? compatibleScalarConversion(
   FieldInfo remoteField,
   FieldInfo localField,
 ) {
-  if (remoteField.fieldType.ref || localField.fieldType.ref) {
+  if (!canConvertCompatibleScalarTypes(
+    remoteField.fieldType,
+    localField.fieldType,
+  )) {
     return null;
   }
+  return CompatibleScalarConversion(remoteField, localField);
+}
+
+bool canConvertCompatibleScalarTypes(
+  FieldType remoteType,
+  FieldType localType,
+) {
+  if (remoteType.ref || localType.ref) {
+    return false;
+  }
   final exactScalarFieldType =
-      remoteField.fieldType.typeId == localField.fieldType.typeId &&
-      remoteField.fieldType.nullable == localField.fieldType.nullable &&
-      remoteField.fieldType.ref == localField.fieldType.ref;
+      remoteType.typeId == localType.typeId &&
+      remoteType.nullable == localType.nullable;
   if (exactScalarFieldType) {
-    return null;
-  }
-  if (!_supportsCompatibleScalarConversion(
-    remoteField.fieldType.typeId,
-    localField.fieldType.typeId,
-  )) {
-    return null;
+    return false;
   }
-  return CompatibleScalarConversion(remoteField, localField);
+  return _supportsCompatibleScalarConversion(
+    remoteType.typeId,
+    localType.typeId,
+  );
 }
 
 CompatibleScalarReadDescriptor compatibleScalarReadDescriptor(
@@ -1243,7 +1252,7 @@ Never _throwScalarConversionError(
   Object cause,
 ) {
   throw InvalidDataException(
-    'Cannot convert compatible field ${conversion.localField.identifier} '
+    'Cannot convert compatible field ${conversion.localField.name} '
     'from type ${conversion.remoteField.fieldType.typeId} '
     'to type ${conversion.localField.fieldType.typeId}: $value',
     cause,
diff --git a/dart/packages/fory/lib/src/serializer/struct_serializer.dart b/dart/packages/fory/lib/src/serializer/struct_serializer.dart
index 0a50cc646..e019927bd 100644
--- a/dart/packages/fory/lib/src/serializer/struct_serializer.dart
+++ b/dart/packages/fory/lib/src/serializer/struct_serializer.dart
@@ -46,10 +46,8 @@ final class StructSerializer extends Serializer<Object?> {
           ),
         ),
       );
-  late final Map<String, SerializationFieldInfo> _localFieldsByIdentifier =
-      <String, SerializationFieldInfo>{
-        for (final field in _localFields) field.identifier: field,
-      };
+  Map<int, SerializationFieldInfo>? _localFieldsById;
+  Map<String, SerializationFieldInfo>? _localFieldsByName;
   final Map<TypeDef, CompatibleStructReadLayout> _compatibleReadLayouts =
       Map<TypeDef, CompatibleStructReadLayout>.identity();
   TypeDef? _lastCompatibleRemoteTypeDef;
@@ -172,6 +170,9 @@ final class StructSerializer extends Serializer<Object?> {
   }
 
   CompatibleStructReadLayout _buildCompatibleReadLayout(TypeDef remoteTypeDef) {
+    _ensureLocalFieldMaps();
+    final localFieldsById = _localFieldsById!;
+    final localFieldsByName = _localFieldsByName!;
     final fields = <CompatibleStructReadField>[];
     for (
       var remoteIndex = 0;
@@ -179,7 +180,11 @@ final class StructSerializer extends Serializer<Object?> {
       remoteIndex += 1
     ) {
       final remoteField = remoteTypeDef.fields[remoteIndex];
-      final localField = _localFieldsByIdentifier[remoteField.identifier];
+      final remoteId = remoteField.id;
+      final localField =
+          remoteId == null
+              ? localFieldsByName[remoteField.identifier]
+              : localFieldsById[remoteId];
       if (localField == null) {
         fields.add(
           CompatibleStructReadField(
@@ -256,6 +261,24 @@ final class StructSerializer extends Serializer<Object?> {
     _lastCompatibleReadLayout = layout;
     return layout;
   }
+
+  void _ensureLocalFieldMaps() {
+    if (_localFieldsById != null) {
+      return;
+    }
+    final fieldsById = <int, SerializationFieldInfo>{};
+    final fieldsByName = <String, SerializationFieldInfo>{};
+    for (final field in _localFields) {
+      final id = field.id;
+      if (id == null) {
+        fieldsByName[field.identifier] = field;
+      } else {
+        fieldsById[id] = field;
+      }
+    }
+    _localFieldsById = fieldsById;
+    _localFieldsByName = fieldsByName;
+  }
 }
 
 bool _topLevelListArrayPair(FieldInfo localField, FieldInfo remoteField) {
@@ -309,11 +332,7 @@ bool _compatibleFieldType(
         localType.arguments.length == remoteType.arguments.length) {
       return true;
     }
-    return compatibleScalarConversion(
-          FieldInfo(name: '', identifier: '', id: null, fieldType: remoteType),
-          FieldInfo(name: '', identifier: '', id: null, fieldType: localType),
-        ) !=
-        null;
+    return canConvertCompatibleScalarTypes(remoteType, localType);
   }
   if (_isStructTypeId(localType.typeId) &&
       _isStructTypeId(remoteType.typeId) &&
diff --git a/dart/packages/fory/pubspec.yaml b/dart/packages/fory/pubspec.yaml
index d497cccfd..5284df482 100644
--- a/dart/packages/fory/pubspec.yaml
+++ b/dart/packages/fory/pubspec.yaml
@@ -28,7 +28,7 @@ environment:
   sdk: ^3.7.0
 
 dependencies:
-  analyzer: '>=8.0.0 <14.0.0'
+  analyzer: '>=8.1.0 <14.0.0'
   build: '>=3.0.0 <5.0.0'
   build_config: '>=1.2.0 <1.4.0'
   meta: '>=1.14.0 <2.0.0'
@@ -36,6 +36,6 @@ dependencies:
 
 dev_dependencies:
   build_runner: '>=2.7.0 <3.0.0'
-  build_test: '>=3.3.1 <3.5.16'
+  build_test: '>=3.5.0 <3.5.16'
   lints: ^6.1.0
   test: ^1.31.1
diff --git a/dart/packages/fory/test/codegen_test_support.dart b/dart/packages/fory/test/codegen_test_support.dart
new file mode 100644
index 000000000..f8c399b4d
--- /dev/null
+++ b/dart/packages/fory/test/codegen_test_support.dart
@@ -0,0 +1,132 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+import 'dart:io';
+import 'dart:isolate';
+
+import 'package:build/build.dart';
+import 'package:build_test/build_test.dart';
+import 'package:fory/src/codegen/fory_builder.dart';
+import 'package:test/test.dart';
+
+const String foryAnnotationImports = '''
+import 'package:fory/src/annotation/fory_field.dart';
+import 'package:fory/src/annotation/fory_struct.dart';
+import 'package:fory/src/annotation/type_spec.dart';
+''';
+
+final Future<Map<String, String>> _annotationAssets = _loadAnnotationAssets();
+
+String foryGeneratedPath(String inputPath) =>
+    inputPath.replaceFirst(RegExp(r'\.dart$'), '.fory.dart');
+
+String foryTestLibrarySource(
+  String inputPath,
+  String body, {
+  String directives = '',
+  String imports = foryAnnotationImports,
+}) {
+  final fileName = inputPath.split('/').last;
+  return '''
+$imports
+$directives
+part '${foryGeneratedPath(fileName)}';
+
+$body
+''';
+}
+
+Future<String> generateForySource({
+  required String inputPath,
+  required String source,
+  Map<String, String> additionalAssets = const <String, String>{},
+}) async {
+  final result = await testBuilder(
+    foryBuilder(BuilderOptions.empty),
+    <String, String>{
+      ...(await _annotationAssets),
+      ...additionalAssets,
+      'fory|$inputPath': source,
+    },
+    rootPackage: 'fory',
+    generateFor: <String>{'fory|$inputPath'},
+    outputs: null,
+    flattenOutput: true,
+  );
+  expect(result.succeeded, isTrue, reason: result.errors.join('\n'));
+  return result.readerWriter.testing.readString(
+    AssetId('fory', foryGeneratedPath(inputPath)),
+  );
+}
+
+Future<void> expectForyGenerationOutput({
+  required String inputPath,
+  required String source,
+  required Matcher output,
+  Map<String, String> additionalAssets = const <String, String>{},
+}) async {
+  expect(
+    await generateForySource(
+      inputPath: inputPath,
+      source: source,
+      additionalAssets: additionalAssets,
+    ),
+    output,
+  );
+}
+
+Future<void> expectForyGenerationError({
+  required String inputPath,
+  required String source,
+  required String message,
+  Map<String, String> additionalAssets = const <String, String>{},
+}) async {
+  final logs = <String>[];
+  final result = await testBuilder(
+    foryBuilder(BuilderOptions.empty),
+    <String, String>{
+      ...(await _annotationAssets),
+      ...additionalAssets,
+      'fory|$inputPath': source,
+    },
+    rootPackage: 'fory',
+    generateFor: <String>{'fory|$inputPath'},
+    onLog: (record) => logs.add(record.message),
+  );
+  expect(result.succeeded, isFalse);
+  expect(logs.join('\n'), contains(message));
+}
+
+Future<Map<String, String>> _loadAnnotationAssets() async {
+  const paths = <String>[
+    'src/annotation/fory_field.dart',
+    'src/annotation/fory_struct.dart',
+    'src/annotation/fory_union.dart',
+    'src/annotation/type_spec.dart',
+  ];
+  final assets = <String, String>{};
+  for (final path in paths) {
+    final uri = await Isolate.resolvePackageUri(
+      Uri.parse('package:fory/$path'),
+    );
+    if (uri == null) {
+      throw StateError('Could not resolve package:fory/$path.');
+    }
+    assets['fory|lib/$path'] = await File.fromUri(uri).readAsString();
+  }
+  return assets;
+}
diff --git a/dart/packages/fory/test/external_type_generator_test.dart b/dart/packages/fory/test/external_type_generator_test.dart
index e806428b2..040041e35 100644
--- a/dart/packages/fory/test/external_type_generator_test.dart
+++ b/dart/packages/fory/test/external_type_generator_test.dart
@@ -18,14 +18,10 @@
 @TestOn('vm')
 library;
 
-import 'dart:io';
-import 'dart:isolate';
-
-import 'package:build/build.dart';
-import 'package:build_test/build_test.dart';
-import 'package:fory/src/codegen/fory_builder.dart';
 import 'package:test/test.dart';
 
+import 'codegen_test_support.dart';
+
 const String _fixtureHeader = '''
 import 'package:fory/src/annotation/fory_field.dart';
 import 'package:fory/src/annotation/fory_struct.dart';
@@ -35,47 +31,19 @@ import 'external_target_fixture.dart';
 part 'external_serializer_fixture.fory.dart';
 ''';
 
-final Future<Map<String, String>> _annotationAssets = _loadAnnotationAssets();
-
-Future<Map<String, String>> _loadAnnotationAssets() async {
-  const paths = <String>[
-    'src/annotation/fory_field.dart',
-    'src/annotation/fory_struct.dart',
-    'src/annotation/fory_union.dart',
-    'src/annotation/type_spec.dart',
-  ];
-  final assets = <String, String>{};
-  for (final path in paths) {
-    final uri = await Isolate.resolvePackageUri(
-      Uri.parse('package:fory/$path'),
-    );
-    if (uri == null) {
-      throw StateError('Could not resolve package:fory/$path.');
-    }
-    assets['fory|lib/$path'] = await File.fromUri(uri).readAsString();
-  }
-  return assets;
-}
-
 Future<void> _expectGenerationError({
   required String targetSource,
   required String declarationSource,
   required String message,
 }) async {
-  final logs = <String>[];
-  await testBuilder(
-    foryBuilder(BuilderOptions.empty),
-    <String, String>{
-      ...(await _annotationAssets),
+  await expectForyGenerationError(
+    inputPath: 'test/external_serializer_fixture.dart',
+    source: '$_fixtureHeader\n$declarationSource',
+    additionalAssets: <String, String>{
       'fory|test/external_target_fixture.dart': targetSource,
-      'fory|test/external_serializer_fixture.dart':
-          '$_fixtureHeader\n$declarationSource',
     },
-    rootPackage: 'fory',
-    generateFor: <String>{'fory|test/external_serializer_fixture.dart'},
-    onLog: (record) => logs.add(record.message),
+    message: message,
   );
-  expect(logs, contains(contains(message)));
 }
 
 Future<void> _expectGenerationOutput({
@@ -85,20 +53,14 @@ Future<void> _expectGenerationOutput({
   String fixtureHeader = _fixtureHeader,
   Map<String, String> additionalAssets = const <String, String>{},
 }) async {
-  await testBuilder(
-    foryBuilder(BuilderOptions.empty),
-    <String, String>{
-      ...(await _annotationAssets),
+  await expectForyGenerationOutput(
+    inputPath: 'test/external_serializer_fixture.dart',
+    source: '$fixtureHeader\n$declarationSource',
+    additionalAssets: <String, String>{
       'fory|test/external_target_fixture.dart': targetSource,
       ...additionalAssets,
-      'fory|test/external_serializer_fixture.dart':
-          '$fixtureHeader\n$declarationSource',
-    },
-    rootPackage: 'fory',
-    generateFor: <String>{'fory|test/external_serializer_fixture.dart'},
-    outputs: <String, Matcher>{
-      'fory|test/external_serializer_fixture.fory.dart': decodedMatches(output),
     },
+    output: output,
   );
 }
 
@@ -599,11 +561,11 @@ abstract final class TargetSerializer {
 ''',
       output: allOf(
         contains('context.reserveGraphMemory(44);'),
-        contains("name: 'value',"),
-        contains("name: 'inherited',"),
-        isNot(contains("name: 'omitted',")),
+        contains("identifier: 'value',"),
+        contains("identifier: 'inherited',"),
+        isNot(contains("identifier: 'omitted',")),
         isNot(contains('_hidden')),
-        isNot(contains("name: 'mixed',")),
+        isNot(contains("identifier: 'mixed',")),
       ),
     );
   });
diff --git a/dart/packages/fory/test/graph_memory_budget_test.dart b/dart/packages/fory/test/graph_memory_budget_test.dart
index ee5218aac..bc2dba480 100644
--- a/dart/packages/fory/test/graph_memory_budget_test.dart
+++ b/dart/packages/fory/test/graph_memory_budget_test.dart
@@ -22,9 +22,11 @@ import 'dart:typed_data';
 import 'package:fory/fory.dart';
 import 'package:fory/src/context/meta_string_reader.dart';
 import 'package:fory/src/context/ref_reader.dart';
+import 'package:fory/src/meta/type_def.dart';
 import 'package:fory/src/resolver/type_resolver.dart';
 import 'package:fory/src/serializer/collection_serializers.dart';
 import 'package:fory/src/serializer/map_serializers.dart';
+import 'package:fory/src/util/hash_util.dart';
 import 'package:test/test.dart';
 
 part 'graph_memory_budget_test.fory.dart';
@@ -94,6 +96,61 @@ class BudgetCompatibleArrayEnvelope {
   Int32List values = Int32List(0);
 }
 
+class BudgetHierarchyBase {
+  BudgetHierarchyBase();
+
+  @ForyField(id: 30, type: Int32Type())
+  int baseValue = 0;
+
+  @ForyField(ignore: true)
+  int ignoredBase = 0;
+}
+
+mixin BudgetHierarchyMixin {
+  @ForyField(id: 10, type: Int32Type())
+  int mixinValue = 0;
+
+  @ForyField(ignore: true)
+  int ignoredMixin = 0;
+}
+
+@ForyStruct()
+class BudgetHierarchyChild extends BudgetHierarchyBase
+    with BudgetHierarchyMixin {
+  BudgetHierarchyChild();
+
+  @ForyField(id: 20, type: Int32Type())
+  int childValue = 0;
+}
+
+@ForyStruct()
+class BudgetHierarchyFlat {
+  BudgetHierarchyFlat();
+
+  @ForyField(id: 20, type: Int32Type())
+  int childValue = 0;
+
+  @ForyField(ignore: true)
+  int ignoredMixin = 0;
+
+  @ForyField(id: 30, type: Int32Type())
+  int baseValue = 0;
+
+  @ForyField(ignore: true)
+  int ignoredBase = 0;
+
+  @ForyField(id: 10, type: Int32Type())
+  int mixinValue = 0;
+}
+
+@ForyStruct()
+class BudgetCompatibleRemote {
+  BudgetCompatibleRemote();
+
+  @ForyField(id: 20, type: Int32Type())
+  int childValue = 0;
+}
+
 final class ThrowsGraphBudget extends Matcher {
   const ThrowsGraphBudget();
 
@@ -156,6 +213,33 @@ void _registerCompatibleArray(Fory fory) {
   );
 }
 
+void _registerHierarchy(Fory fory, Type type) {
+  GraphMemoryBudgetTestForyModule.register(fory, type, id: 410);
+}
+
+void _registerCompatibleRemote(Fory fory) {
+  GraphMemoryBudgetTestForyModule.register(
+    fory,
+    BudgetCompatibleRemote,
+    name: 'test.BudgetHierarchy',
+  );
+}
+
+void _registerCompatibleHierarchy(Fory fory) {
+  GraphMemoryBudgetTestForyModule.register(
+    fory,
+    BudgetHierarchyChild,
+    name: 'test.BudgetHierarchy',
+  );
+}
+
+TypeDef _hierarchyTypeDef(Type type) {
+  _registerHierarchy(Fory(), type);
+  final resolver = TypeResolver(Config());
+  resolver.registerGenerated(type, id: 410, namespace: null, typeName: null);
+  return resolver.typeDefForResolved(resolver.resolvedRegisteredType(type));
+}
+
 ReadContext _readContext(
   Buffer buffer, {
   int maxGraphMemoryBytes = _defaultGraphMemoryBytes,
@@ -283,6 +367,36 @@ void main() {
       expect(roundTrip.ignored, isNull);
     });
 
+    test('reserves one flattened hierarchy owner', () {
+      final writer = Fory();
+      _registerHierarchy(writer, BudgetHierarchyChild);
+      final bytes = writer.serialize(
+        BudgetHierarchyChild()
+          ..baseValue = 30
+          ..ignoredBase = 31
+          ..mixinValue = 10
+          ..ignoredMixin = 11
+          ..childValue = 20,
+      );
+      final required = _objectGraphBytes(5);
+
+      final failingReader = Fory(maxGraphMemoryBytes: required - 1);
+      _registerHierarchy(failingReader, BudgetHierarchyChild);
+      expect(
+        () => failingReader.deserialize<BudgetHierarchyChild>(bytes),
+        _throwsGraphBudget,
+      );
+
+      final passingReader = Fory(maxGraphMemoryBytes: required);
+      _registerHierarchy(passingReader, BudgetHierarchyChild);
+      final roundTrip = passingReader.deserialize<BudgetHierarchyChild>(bytes);
+      expect(roundTrip.baseValue, equals(30));
+      expect(roundTrip.mixinValue, equals(10));
+      expect(roundTrip.childValue, equals(20));
+      expect(roundTrip.ignoredBase, isZero);
+      expect(roundTrip.ignoredMixin, isZero);
+    });
+
     test('reserves map entries', () {
       final value = <Object?, Object?>{'a': 1};
 
@@ -381,6 +495,35 @@ void main() {
       );
     });
 
+    test('compatible reads reserve the local flattened shape', () {
+      final writer = Fory(compatible: true);
+      _registerCompatibleRemote(writer);
+      final bytes = writer.serialize(BudgetCompatibleRemote()..childValue = 20);
+      final required = _objectGraphBytes(5);
+
+      final failingReader = Fory(
+        compatible: true,
+        maxGraphMemoryBytes: required - 1,
+      );
+      _registerCompatibleHierarchy(failingReader);
+      expect(
+        () => failingReader.deserialize<BudgetHierarchyChild>(bytes),
+        _throwsGraphBudget,
+      );
+
+      final passingReader = Fory(
+        compatible: true,
+        maxGraphMemoryBytes: required,
+      );
+      _registerCompatibleHierarchy(passingReader);
+      final roundTrip = passingReader.deserialize<BudgetHierarchyChild>(bytes);
+      expect(roundTrip.baseValue, isZero);
+      expect(roundTrip.mixinValue, isZero);
+      expect(roundTrip.childValue, equals(20));
+      expect(roundTrip.ignoredBase, isZero);
+      expect(roundTrip.ignoredMixin, isZero);
+    });
+
     test('skips strings binary and dense typed arrays', () {
       final fory = Fory(maxGraphMemoryBytes: 1);
       final text = List<String>.filled(128, 'x').join();
@@ -415,4 +558,24 @@ void main() {
       );
     });
   });
+
+  group('flattened hierarchy schema', () {
+    test('matches flat TypeDef bytes hash and global order', () {
+      final inherited = _hierarchyTypeDef(BudgetHierarchyChild);
+      final flat = _hierarchyTypeDef(BudgetHierarchyFlat);
+
+      expect(
+        inherited.fields.map((field) => field.id),
+        orderedEquals(<int>[10, 20, 30]),
+      );
+      expect(
+        flat.fields.map((field) => field.id),
+        orderedEquals(<int>[10, 20, 30]),
+      );
+      expect(inherited.encoded, orderedEquals(flat.encoded));
+      expect(inherited.header, equals(flat.header));
+      expect(schemaFingerprint(inherited), equals(schemaFingerprint(flat)));
+      expect(schemaHash(inherited), equals(schemaHash(flat)));
+    });
+  });
 }
diff --git a/dart/packages/fory/test/runtime_validation_test.dart b/dart/packages/fory/test/runtime_validation_test.dart
index 13a74ce2a..f86c2f096 100644
--- a/dart/packages/fory/test/runtime_validation_test.dart
+++ b/dart/packages/fory/test/runtime_validation_test.dart
@@ -136,17 +136,6 @@ class SchemaVersionV2 {
   int count = 0;
 }
 
-@ForyStruct()
-class DuplicateFieldIdOrder {
-  DuplicateFieldIdOrder();
-
-  @ForyField(id: 1)
-  String first = '';
-
-  @ForyField(id: 1)
-  String second = '';
-}
-
 void _registerValidationTypes(Fory fory) {
   RuntimeValidationTestForyModule.register(
     fory,
@@ -217,25 +206,6 @@ Object _nestedList(int depth) {
 
 void main() {
   group('field options', () {
-    test('duplicate generated field ids are rejected during registration', () {
-      final fory = Fory();
-
-      expect(
-        () => RuntimeValidationTestForyModule.register(
-          fory,
-          DuplicateFieldIdOrder,
-          name: 'validation.DuplicateFieldIdOrder',
-        ),
-        throwsA(
-          isA<ArgumentError>().having(
-            (error) => error.toString(),
-            'message',
-            contains('Duplicate field id 1'),
-          ),
-        ),
-      );
-    });
-
     test('ignored fields stay local only after round trip', () {
       final fory = Fory();
       _registerValidationTypes(fory);
diff --git a/dart/packages/fory/test/struct_constructor_flow_generator_test.dart b/dart/packages/fory/test/struct_constructor_flow_generator_test.dart
new file mode 100644
index 000000000..e433a4704
--- /dev/null
+++ b/dart/packages/fory/test/struct_constructor_flow_generator_test.dart
@@ -0,0 +1,544 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+@TestOn('vm')
+library;
+
+import 'package:test/test.dart';
+
+import 'codegen_test_support.dart';
+
+const String _fixtureHeader = '''
+import 'package:fory/src/annotation/fory_field.dart';
+import 'package:fory/src/annotation/fory_struct.dart';
+
+part 'struct_constructor_flow_fixture.fory.dart';
+''';
+
+Future<void> _expectGenerationOutput({
+  required String source,
+  required Matcher output,
+}) async {
+  await expectForyGenerationOutput(
+    inputPath: 'test/struct_constructor_flow_fixture.dart',
+    source: '$_fixtureHeader\n$source',
+    output: output,
+  );
+}
+
+Future<void> _expectGenerationError({
+  required String source,
+  required String message,
+}) async {
+  await expectForyGenerationError(
+    inputPath: 'test/struct_constructor_flow_fixture.dart',
+    source: '$_fixtureHeader\n$source',
+    message: message,
+  );
+}
+
+void main() {
+  group('accepted identity flow', () {
+    test('field formal', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+final class FieldFormalValue {
+  final int value;
+
+  FieldFormalValue(this.value);
+}
+''',
+        output: contains('final value = FieldFormalValue(_valueValue);'),
+      );
+    });
+
+    test('multi-level super formal', () async {
+      await _expectGenerationOutput(
+        source: '''
+class SuperFormalBase<T> {
+  final T value;
+
+  SuperFormalBase(this.value);
+}
+
+class SuperFormalMiddle<T> extends SuperFormalBase<T> {
+  SuperFormalMiddle(super.value);
+}
+
+@ForyStruct()
+final class SuperFormalLeaf extends SuperFormalMiddle<int> {
+  SuperFormalLeaf(super.value);
+}
+''',
+        output: allOf(
+          contains('final int _valueValue'),
+          contains('final value = SuperFormalLeaf(_valueValue);'),
+        ),
+      );
+    });
+
+    test('renamed explicit initializer', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+final class RenamedInitializerValue {
+  final int stored;
+
+  RenamedInitializerValue(int decoded) : stored = decoded;
+}
+''',
+        output: contains(
+          'final value = RenamedInitializerValue(_storedValue);',
+        ),
+      );
+    });
+
+    test('named super constructor and argument', () async {
+      await _expectGenerationOutput(
+        source: '''
+class NamedSuperBase {
+  final int stored;
+
+  NamedSuperBase.named({required this.stored});
+}
+
+@ForyStruct()
+final class NamedSuperChild extends NamedSuperBase {
+  NamedSuperChild({required int decoded})
+    : super.named(stored: decoded);
+}
+''',
+        output: contains(
+          'final value = NamedSuperChild(decoded: _storedValue);',
+        ),
+      );
+    });
+
+    test('redirect', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+final class RedirectedValue {
+  final int stored;
+
+  RedirectedValue(int decoded) : this.named(decoded);
+
+  RedirectedValue.named(int forwarded) : stored = forwarded;
+}
+''',
+        output: contains('final value = RedirectedValue(_storedValue);'),
+      );
+    });
+
+    test('parenthesized direct reference', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+final class ParenthesizedValue {
+  final int stored;
+
+  ParenthesizedValue(int decoded) : stored = (((decoded)));
+}
+''',
+        output: contains('final value = ParenthesizedValue(_storedValue);'),
+      );
+    });
+
+    test('one final and writable destination', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+final class FinalAndWritableValue {
+  final int fixed;
+  int mutable;
+
+  FinalAndWritableValue(int decoded)
+    : fixed = decoded,
+      mutable = decoded;
+}
+''',
+        output: allOf(
+          contains('final value = FinalAndWritableValue(_fixedValue);'),
+          contains('value.mutable = _mutableValue;'),
+        ),
+      );
+    });
+
+    test('passes an optional writable identity flow', () async {
+      await _expectGenerationOutput(
+        source: '''
+class OptionalMutableBase {
+  int mutable;
+
+  OptionalMutableBase([int decoded = 0]) : mutable = decoded;
+}
+
+@ForyStruct()
+final class OptionalMutableChild extends OptionalMutableBase {
+  final int fixed;
+
+  OptionalMutableChild(this.fixed, [int decoded = 0]) : super(decoded);
+}
+''',
+        output: allOf(
+          contains(
+            'final value = OptionalMutableChild('
+            '_fixedValue, _mutableValue);',
+          ),
+          isNot(contains('value.mutable = _mutableValue;')),
+        ),
+      );
+    });
+
+    test('analyzes optional flow for an all-writable struct', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+final class AllWritableOptionalValue {
+  int value;
+
+  @ForyField(ignore: true)
+  final int observedAtConstruction;
+
+  AllWritableOptionalValue([int decoded = 0])
+    : value = decoded,
+      observedAtConstruction = decoded;
+}
+''',
+        output: allOf(
+          contains('final value = AllWritableOptionalValue(_valueValue);'),
+          isNot(contains('value.value = _valueValue;')),
+        ),
+      );
+    });
+
+    test('keeps an unbound optional constructor mutable', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+final class UnboundOptionalSelf {
+  UnboundOptionalSelf({int ignored = 0});
+
+  @ForyField(ref: true)
+  UnboundOptionalSelf? next;
+}
+''',
+        output: allOf(
+          contains('final value = UnboundOptionalSelf();'),
+          contains('context.reference(value);'),
+          isNot(contains('ignored:')),
+        ),
+      );
+    });
+
+    test('omits an unbound optional named parameter', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+final class OptionalNamedValue {
+  final int value;
+
+  OptionalNamedValue({int ignored = 0, this.value = 0});
+}
+''',
+        output: allOf(
+          contains('final value = OptionalNamedValue(value: _valueValue);'),
+          isNot(contains('ignored:')),
+        ),
+      );
+    });
+
+    test('keeps generic constructor specializations separate', () async {
+      await _expectGenerationOutput(
+        source: '''
+class GenericBase<T> {
+  final T value;
+
+  GenericBase(T decoded) : value = decoded;
+}
+
+@ForyStruct()
+final class GenericIntChild extends GenericBase<int> {
+  GenericIntChild(int decoded) : super(decoded);
+}
+
+@ForyStruct()
+final class GenericStringChild extends GenericBase<String> {
+  GenericStringChild(String decoded) : super(decoded);
+}
+''',
+        output: allOf(
+          contains('final int _valueValue'),
+          contains('final String _valueValue'),
+          contains('final value = GenericIntChild(_valueValue);'),
+          contains('final value = GenericStringChild(_valueValue);'),
+        ),
+      );
+    });
+
+    test('writes only unbound mutable fields', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+final class CanonicalPostWrites {
+  int zeta;
+  int beta = 0;
+  int alpha = 0;
+
+  CanonicalPostWrites(this.zeta);
+}
+''',
+        output: predicate<String>((generated) {
+          final alphaWrite = generated.indexOf('value.alpha = _alphaValue;');
+          final betaWrite = generated.indexOf('value.beta = _betaValue;');
+          return alphaWrite >= 0 &&
+              betaWrite >= 0 &&
+              alphaWrite < betaWrite &&
+              !generated.contains('value.zeta = _zetaValue;');
+        }, 'writes unbound mutable fields in canonical schema order'),
+      );
+    });
+  });
+
+  group('rejected identity flow', () {
+    test('same-name unused parameter', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class SameNameUnusedValue {
+  int value = 0;
+
+  SameNameUnusedValue(int value);
+}
+''',
+        message: 'name equality is not constructor identity proof',
+      );
+    });
+
+    test('function call', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class CallValue {
+  final int value;
+
+  CallValue(int decoded) : value = identity(decoded);
+
+  static int identity(int value) => value;
+}
+''',
+        message: 'transforms or does not directly reference',
+      );
+    });
+
+    test('operator', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class OperatorValue {
+  final int value;
+
+  OperatorValue(int decoded) : value = decoded + 0;
+}
+''',
+        message: 'transforms or does not directly reference',
+      );
+    });
+
+    test('cast', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class CastValue {
+  final int value;
+
+  CastValue(Object decoded) : value = decoded as int;
+}
+''',
+        message: 'transforms or does not directly reference',
+      );
+    });
+
+    test('null assertion', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class NullAssertionValue {
+  final int value;
+
+  NullAssertionValue(int? decoded) : value = decoded!;
+}
+''',
+        message: 'transforms or does not directly reference',
+      );
+    });
+
+    test('constant', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class ConstantValue {
+  final int value;
+
+  ConstantValue(int decoded) : value = 1;
+}
+''',
+        message: 'transforms or does not directly reference',
+      );
+    });
+
+    test('constructor body assignment', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class BodyAssignmentValue {
+  late final int value;
+
+  BodyAssignmentValue(int decoded) {
+    value = decoded;
+  }
+}
+''',
+        message: 'is not initialized from a parameter',
+      );
+    });
+
+    test('final declaration initializer', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class FinalInitializerValue {
+  final int value = 1;
+
+  FinalInitializerValue();
+}
+''',
+        message: 'has a declaration initializer',
+      );
+    });
+
+    test('late-final declaration initializer', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class LateFinalInitializerValue {
+  late final int value = 1;
+
+  LateFinalInitializerValue();
+}
+''',
+        message: 'has a declaration initializer',
+      );
+    });
+
+    test('one root for multiple final fields', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class AmbiguousFinalValue {
+  final int first;
+  final int second;
+
+  AmbiguousFinalValue(int decoded)
+    : first = decoded,
+      second = decoded;
+}
+''',
+        message: 'initializes multiple non-writable fields',
+      );
+    });
+
+    test('required root with ambiguous writable fields', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class AmbiguousWritableValue {
+  int first;
+  int second;
+
+  AmbiguousWritableValue(int decoded)
+    : first = decoded,
+      second = decoded;
+}
+''',
+        message: 'has multiple writable identity-flow sources',
+      );
+    });
+
+    test('optional root with ambiguous writable fields', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class AmbiguousOptionalValue {
+  final int fixed;
+  int first;
+  int second;
+
+  AmbiguousOptionalValue(this.fixed, [int decoded = 0])
+    : first = decoded,
+      second = decoded;
+}
+''',
+        message: 'has multiple writable identity-flow sources',
+      );
+    });
+
+    test('required parameter without a field source', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class MissingRequiredSourceValue {
+  int stored = 0;
+
+  MissingRequiredSourceValue(int decoded);
+}
+''',
+        message: 'has no serialized field connected by an exact',
+      );
+    });
+
+    test('optional positional gap', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class OptionalGapValue {
+  final int value;
+
+  OptionalGapValue([int ignored = 0, this.value = 0]);
+}
+''',
+        message: 'after an omitted optional positional parameter',
+      );
+    });
+
+    test('reference-tracked self path', () async {
+      await _expectGenerationError(
+        source: '''
+@ForyStruct()
+final class SelfReferenceValue {
+  @ForyField(ref: true)
+  final SelfReferenceValue? next;
+
+  SelfReferenceValue(this.next);
+}
+''',
+        message: 'cannot bind reference-tracked self paths',
+      );
+    });
+  });
+}
diff --git a/dart/packages/fory/test/struct_inheritance_generator_test.dart b/dart/packages/fory/test/struct_inheritance_generator_test.dart
new file mode 100644
index 000000000..b5475416b
--- /dev/null
+++ b/dart/packages/fory/test/struct_inheritance_generator_test.dart
@@ -0,0 +1,625 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+@TestOn('vm')
+library;
+
+import 'package:test/test.dart';
+
+import 'codegen_test_support.dart';
+
+const String _inputPath = 'test/struct_inheritance_generator_fixture.dart';
+
+const String _fixtureHeader = '''
+import 'package:fory/src/annotation/fory_field.dart';
+import 'package:fory/src/annotation/fory_struct.dart';
+import 'package:fory/src/annotation/type_spec.dart';
+
+part 'struct_inheritance_generator_fixture.fory.dart';
+''';
+
+Future<void> _expectGenerationOutput({
+  required String source,
+  required Matcher output,
+  String fixtureHeader = _fixtureHeader,
+  Map<String, String> additionalAssets = const <String, String>{},
+}) async {
+  await expectForyGenerationOutput(
+    inputPath: _inputPath,
+    source: '$fixtureHeader\n$source',
+    output: output,
+    additionalAssets: additionalAssets,
+  );
+}
+
+Future<void> _expectGenerationError({
+  required String source,
+  required String message,
+  String fixtureHeader = _fixtureHeader,
+  Map<String, String> additionalAssets = const <String, String>{},
+}) async {
+  await expectForyGenerationError(
+    inputPath: _inputPath,
+    source: '$fixtureHeader\n$source',
+    message: message,
+    additionalAssets: additionalAssets,
+  );
+}
+
+Matcher _wireFieldsInOrder(List<String> names) {
+  return predicate<String>((output) {
+    var offset = 0;
+    for (final name in names) {
+      final next = output.indexOf("identifier: '$name',", offset);
+      if (next < 0) {
+        return false;
+      }
+      offset = next + 1;
+    }
+    return true;
+  }, 'contains wire fields in order: ${names.join(', ')}');
+}
+
+Matcher _containsOnce(String text) {
+  return predicate<String>(
+    (output) => RegExp(RegExp.escape(text)).allMatches(output).length == 1,
+    'contains exactly once: $text',
+  );
+}
+
+void main() {
+  group('ordinary hierarchy discovery', () {
+    test('flattens superclass and mixin storage exactly once', () async {
+      await _expectGenerationOutput(
+        source: '''
+abstract interface class Contract {
+  Object? get interfaceOnly;
+}
+
+abstract class Grand {
+  Object? grand;
+  static Object? staticOnly;
+
+  Object? get abstractOnly;
+}
+
+class Parent extends Grand {
+  Object? parent;
+
+  @override
+  Object? get abstractOnly => parent;
+}
+
+mixin M1 {
+  Object? m1;
+}
+
+mixin M2 {
+  Object? m2;
+}
+
+@ForyStruct()
+class Child extends Parent with M1, M2 implements Contract {
+  Child();
+
+  Object? child;
+
+  @override
+  Object? get interfaceOnly => child;
+}
+''',
+        output: allOf(<Matcher>[
+          _wireFieldsInOrder(const <String>[
+            'child',
+            'grand',
+            'm1',
+            'm2',
+            'parent',
+          ]),
+          _containsOnce("identifier: 'grand',"),
+          _containsOnce("identifier: 'parent',"),
+          _containsOnce("identifier: 'm1',"),
+          _containsOnce("identifier: 'm2',"),
+          _containsOnce("identifier: 'child',"),
+          contains(RegExp(r'value\.grand = _readChildGrand\(')),
+          contains(RegExp(r'value\.parent = _readChildParent\(')),
+          contains(RegExp(r'value\.m1 = _readChildM1\(')),
+          contains(RegExp(r'value\.m2 = _readChildM2\(')),
+          contains(RegExp(r'value\.child = _readChildChild\(')),
+          contains('context.reserveGraphMemory(44);'),
+          isNot(contains("identifier: 'interface_only',")),
+          isNot(contains("identifier: 'abstract_only',")),
+          isNot(contains("identifier: 'static_only',")),
+        ]),
+      );
+    });
+
+    test('flattens a named generic mixin application', () async {
+      await _expectGenerationOutput(
+        source: '''
+class AliasBase<T> {
+  final T baseValue;
+
+  AliasBase(this.baseValue);
+}
+
+mixin AliasMixin<T> {
+  T? mixinValue;
+}
+
+class NamedApplication<T> = AliasBase<T> with AliasMixin<T>;
+
+@ForyStruct()
+class AliasChild extends NamedApplication<int> {
+  AliasChild(super.baseValue);
+
+  int childValue = 0;
+}
+''',
+        output: allOf(
+          contains('final int _baseValueValue'),
+          contains('final int? _mixinValueValue'),
+          contains('final int _childValueValue'),
+          contains('final value = AliasChild(_baseValueValue);'),
+          contains('context.reserveGraphMemory(36);'),
+        ),
+      );
+    });
+
+    test('substitutes inherited generic fields in each layer', () async {
+      await _expectGenerationOutput(
+        source: '''
+class Base<T> {
+  late List<T> items;
+}
+
+class Middle<U> extends Base<U> {}
+
+@ForyStruct()
+class StringChild extends Middle<String> {
+  StringChild();
+}
+''',
+        output: allOf(
+          contains("identifier: 'items',"),
+          contains('type: List<String>,'),
+          contains('value.items'),
+          isNot(contains('type: List<T>,')),
+          isNot(contains('type: List<U>,')),
+        ),
+      );
+    });
+
+    test('uses public storage from another library directly', () async {
+      await _expectGenerationOutput(
+        fixtureHeader: '''
+import 'package:fory/src/annotation/fory_struct.dart';
+import 'public_inheritance_parent.dart';
+
+part 'struct_inheritance_generator_fixture.fory.dart';
+''',
+        additionalAssets: const <String, String>{
+          'fory|test/public_inheritance_parent.dart': '''
+class PublicParent {
+  Object? inherited;
+}
+''',
+        },
+        source: '''
+@ForyStruct()
+class PublicChild extends PublicParent {
+  PublicChild();
+}
+''',
+        output: allOf(
+          contains("identifier: 'inherited',"),
+          contains('value.inherited'),
+          isNot(contains('ForyFieldAccess')),
+        ),
+      );
+    });
+
+    test('rejects an ambiguous inherited field type name', () async {
+      await _expectGenerationError(
+        fixtureHeader: '''
+import 'package:fory/src/annotation/fory_struct.dart';
+import 'typed_inheritance_parent.dart';
+import 'conflicting_type.dart';
+
+part 'struct_inheritance_generator_fixture.fory.dart';
+''',
+        additionalAssets: const <String, String>{
+          'fory|test/typed_inheritance_parent.dart': '''
+class SharedValue {}
+
+class TypedParent {
+  SharedValue? inherited;
+}
+''',
+          'fory|test/conflicting_type.dart': 'class SharedValue {}',
+        },
+        source: '''
+@ForyStruct()
+class TypedChild extends TypedParent {
+  TypedChild();
+}
+''',
+        message: 'SharedValue, which is not nameable',
+      );
+    });
+
+    test('uses an unambiguous prefix for an inherited field type', () async {
+      await _expectGenerationOutput(
+        fixtureHeader: '''
+import 'package:fory/src/annotation/fory_struct.dart';
+import 'typed_inheritance_parent.dart';
+import 'typed_inheritance_parent.dart' as parent;
+import 'conflicting_type.dart';
+
+part 'struct_inheritance_generator_fixture.fory.dart';
+''',
+        additionalAssets: const <String, String>{
+          'fory|test/typed_inheritance_parent.dart': '''
+class SharedValue {}
+
+class TypedParent {
+  SharedValue? inherited;
+}
+''',
+          'fory|test/conflicting_type.dart': 'class SharedValue {}',
+        },
+        source: '''
+@ForyStruct()
+class TypedChild extends TypedParent {
+  TypedChild();
+}
+''',
+        output: allOf(
+          contains('type: parent.SharedValue,'),
+          contains('parent.SharedValue? _readTypedChildInherited('),
+          contains('value.inherited'),
+        ),
+      );
+    });
+
+    test('rejects a deferred inherited field type namespace', () async {
+      await _expectGenerationError(
+        fixtureHeader: '''
+import 'package:fory/src/annotation/fory_struct.dart';
+import 'deferred_type_parent.dart';
+import 'shared_value.dart' deferred as values;
+
+part 'struct_inheritance_generator_fixture.fory.dart';
+''',
+        additionalAssets: const <String, String>{
+          'fory|test/shared_value.dart': 'class SharedValue {}',
+          'fory|test/deferred_type_parent.dart': '''
+import 'shared_value.dart';
+
+class DeferredTypeParent {
+  SharedValue? inherited;
+}
+''',
+        },
+        source: '''
+@ForyStruct()
+class DeferredTypeChild extends DeferredTypeParent {
+  DeferredTypeChild();
+}
+''',
+        message: 'SharedValue, which is not nameable',
+      );
+    });
+
+    test('uses same-library private storage directly', () async {
+      await _expectGenerationOutput(
+        source: '''
+class PrivateParent {
+  Object? _secret;
+}
+
+@ForyStruct()
+class PrivateChild extends PrivateParent {
+  PrivateChild();
+}
+''',
+        output: allOf(
+          contains("identifier: '_secret',"),
+          contains('value._secret'),
+          isNot(contains('ForyFieldAccess')),
+        ),
+      );
+    });
+
+    test('reconstructs same-library private final storage directly', () async {
+      await _expectGenerationOutput(
+        source: '''
+class PrivateFinalParent {
+  final int _secret;
+
+  PrivateFinalParent(this._secret);
+}
+
+@ForyStruct()
+class PrivateFinalChild extends PrivateFinalParent {
+  PrivateFinalChild(int decoded) : super(decoded);
+}
+''',
+        output: allOf(
+          contains("identifier: '_secret',"),
+          contains('value._secret'),
+          contains('final value = PrivateFinalChild(__secretValue);'),
+          isNot(contains('ForyFieldAccess')),
+        ),
+      );
+    });
+
+    test(
+      'ignores only declaration-owned fields before access checks',
+      () async {
+        await _expectGenerationOutput(
+          fixtureHeader: '''
+import 'package:fory/src/annotation/fory_struct.dart';
+import 'ignored_inheritance_parent.dart';
+
+part 'struct_inheritance_generator_fixture.fory.dart';
+''',
+          additionalAssets: const <String, String>{
+            'fory|test/ignored_inheritance_parent.dart': '''
+import 'package:fory/src/annotation/fory_field.dart';
+
+class IgnoredParent {
+  @ForyField(ignore: true)
+  Object? _ignored;
+
+  Object? visible;
+}
+''',
+          },
+          source: '''
+@ForyStruct()
+class IgnoreChild extends IgnoredParent {
+  IgnoreChild();
+}
+''',
+          output: allOf(
+            contains("identifier: 'visible',"),
+            contains('value.visible'),
+            contains('context.reserveGraphMemory(32);'),
+            isNot(contains('_ignored')),
+            isNot(contains('ForyFieldAccess')),
+          ),
+        );
+      },
+    );
+
+    test('rejects an unsupported inherited effective type', () async {
+      await _expectGenerationError(
+        source: '''
+int _identity(int value) => value;
+
+class UnsupportedParent {
+  int Function(int) callback = _identity;
+}
+
+@ForyStruct()
+class UnsupportedChild extends UnsupportedParent {
+  UnsupportedChild();
+}
+''',
+        message: 'has unsupported effective type int Function(int)',
+      );
+    });
+
+    test(
+      'ignored unsupported inherited storage bypasses type analysis',
+      () async {
+        await _expectGenerationOutput(
+          source: '''
+int _identity(int value) => value;
+
+class IgnoredUnsupportedParent {
+  @ForyField(ignore: true)
+  int Function(int) callback = _identity;
+}
+
+@ForyStruct()
+class IgnoredUnsupportedChild extends IgnoredUnsupportedParent {
+  IgnoredUnsupportedChild();
+
+  int value = 0;
+}
+''',
+          output: allOf(
+            contains("identifier: 'value',"),
+            contains('context.reserveGraphMemory(32);'),
+            isNot(contains('callback')),
+          ),
+        );
+      },
+    );
+  });
+
+  group('hierarchy validation', () {
+    test('rejects hidden ancestor storage', () async {
+      await _expectGenerationError(
+        source: '''
+class HiddenBase {
+  Object? value;
+}
+
+@ForyStruct()
+class HiddenChild extends HiddenBase {
+  HiddenChild();
+
+  @override
+  Object? value;
+}
+''',
+        message: 'later field or accessor hides the storage',
+      );
+    });
+
+    test('rejects a repeated mixin storage application', () async {
+      await _expectGenerationError(
+        source: '''
+class RepeatedBase {}
+
+mixin RepeatedMixin {
+  int value = 0;
+}
+
+class FirstApplication = RepeatedBase with RepeatedMixin;
+class SecondApplication = FirstApplication with RepeatedMixin;
+
+@ForyStruct()
+class RepeatedChild extends SecondApplication {}
+''',
+        message: 'the same field declaration is applied more than once',
+      );
+    });
+
+    test('rejects duplicate ids across the flattened schema', () async {
+      await _expectGenerationError(
+        source: '''
+class IdBase {
+  @ForyField(id: 7)
+  int baseValue = 0;
+}
+
+@ForyStruct()
+class IdChild extends IdBase {
+  IdChild();
+
+  @ForyField(id: 7)
+  int childValue = 0;
+}
+''',
+        message: 'duplicate field id 7',
+      );
+    });
+
+    test('rejects duplicate canonical names across hierarchy layers', () async {
+      await _expectGenerationError(
+        source: '''
+class NameBase {
+  int fooBar = 0;
+}
+
+@ForyStruct()
+class NameChild extends NameBase {
+  NameChild();
+
+  int foo_bar = 0;
+}
+''',
+        message: 'duplicate canonical field name foo_bar',
+      );
+    });
+
+    test('uses indexed generated names when source names collide', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+class CodegenNameCollision {
+  CodegenNameCollision(this.foo, this.Foo);
+
+  @ForyField(id: 1)
+  final int foo;
+
+  @ForyField(id: 2)
+  final int Foo;
+}
+''',
+        output: allOf(
+          contains('final int _field0Value'),
+          contains('final int _field1Value'),
+          contains('_readCodegenNameCollisionField0('),
+          contains('_readCodegenNameCollisionField1('),
+          contains(
+            'final value = CodegenNameCollision(_field0Value, _field1Value);',
+          ),
+        ),
+      );
+    });
+
+    test('sorts from normalized TypeSpec nullability', () async {
+      await _expectGenerationOutput(
+        source: '''
+@ForyStruct()
+class NormalizedSort {
+  NormalizedSort();
+
+  @ForyField(type: Int64Type(nullable: true))
+  int aForcedBoxed = 0;
+
+  int zPrimitive = 0;
+}
+''',
+        output: allOf(
+          _wireFieldsInOrder(const <String>['z_primitive', 'a_forced_boxed']),
+          predicate<String>((output) {
+            final fieldStart = output.indexOf("identifier: 'a_forced_boxed',");
+            final fieldEnd = output.indexOf('),', fieldStart);
+            if (fieldStart < 0 || fieldEnd < 0) {
+              return false;
+            }
+            return output
+                .substring(fieldStart, fieldEnd)
+                .contains('nullable: true,');
+          }, 'uses normalized nullable metadata for a_forced_boxed'),
+        ),
+      );
+    });
+  });
+
+  test('keeps external target hierarchy schema explicit', () async {
+    await _expectGenerationOutput(
+      fixtureHeader: '''
+import 'package:fory/src/annotation/fory_struct.dart';
+import 'external_inheritance_target.dart';
+
+part 'struct_inheritance_generator_fixture.fory.dart';
+''',
+      additionalAssets: const <String, String>{
+        'fory|test/external_inheritance_target.dart': '''
+class ExternalBase {
+  int inherited = 0;
+  int omittedBase = 0;
+}
+
+class ExternalChild extends ExternalBase {
+  int omittedChild = 0;
+}
+''',
+      },
+      source: '''
+@ForyStruct(target: ExternalChild)
+abstract final class ExternalChildSchema {
+  late final int inherited;
+}
+''',
+      output: allOf(
+        _containsOnce("identifier: 'inherited',"),
+        contains('value.inherited'),
+        contains('context.reserveGraphMemory(36);'),
+        isNot(contains("identifier: 'omitted_base',")),
+        isNot(contains("identifier: 'omitted_child',")),
+      ),
+    );
+  });
+}
diff --git a/dart/packages/fory/test/struct_private_companion_generator_test.dart b/dart/packages/fory/test/struct_private_companion_generator_test.dart
new file mode 100644
index 000000000..0ec50e799
--- /dev/null
+++ b/dart/packages/fory/test/struct_private_companion_generator_test.dart
@@ -0,0 +1,1005 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements.  See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership.  The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License.  You may obtain a copy of the License at
+//
+//   http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied.  See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+@TestOn('vm')
+library;
+
+import 'package:test/test.dart';
+
+import 'codegen_test_support.dart';
+
+const String _annotationImports = '''
+import 'package:fory/src/annotation/fory_field.dart';
+import 'package:fory/src/annotation/fory_struct.dart';
+''';
+
+String _librarySource(String inputPath, String body, {String directives = ''}) {
+  return foryTestLibrarySource(
+    inputPath,
+    body,
+    directives: directives,
+    imports: _annotationImports,
+  );
+}
+
+Future<String> _generate({
+  required String inputPath,
+  required String source,
+  Map<String, String> additionalAssets = const <String, String>{},
+}) async {
+  return generateForySource(
+    inputPath: inputPath,
+    source: source,
+    additionalAssets: additionalAssets,
+  );
+}
+
+Future<void> _expectGenerationError({
+  required String inputPath,
+  required String source,
+  required String message,
+  Map<String, String> additionalAssets = const <String, String>{},
+}) async {
+  await expectForyGenerationError(
+    inputPath: inputPath,
+    source: source,
+    message: message,
+    additionalAssets: additionalAssets,
+  );
+}
+
+String _providerSource({
+  String inputPath = 'test/private_provider.dart',
+  String boundaryName = 'Boundary',
+}) {
+  return _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class $boundaryName {
+  int _secret = 0;
+}
+''');
+}
+
+void _expectOnlyVisibilityBridge(String output, Iterable<String> owners) {
+  for (final owner in owners) {
+    expect(
+      output,
+      contains(
+        r'abstract final class $'
+        '${owner}ForyFieldAccess',
+      ),
+    );
+    expect(output, isNot(contains('_${owner}ForySerializer')));
+  }
+  expect(output, isNot(contains('GeneratedStructSchema<')));
+  expect(output, isNot(contains('GeneratedStructSerializer<')));
+  expect(output, isNot(contains('ReadContext')));
+  expect(output, isNot(contains('WriteContext')));
+  expect(output, isNot(contains('Serializer<')));
+  expect(output, isNot(contains('ForyModule')));
+  expect(output, isNot(contains('Object?')));
+  expect(output, isNot(contains('dynamic')));
+  expect(output, isNot(contains('Function')));
+  expect(output, isNot(contains('callback')));
+  expect(output, isNot(contains('reflection')));
+  expect(output, isNot(contains('register')));
+}
+
+void main() {
+  group('provider ownership', () {
+    test('supports abstract, open generic, and mixin providers', () async {
+      const inputPath = 'test/provider_shapes.dart';
+      final output = await _generate(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class AbstractBoundary {
+  int _abstractValue = 0;
+}
+
+@ForyStruct(exposePrivateFields: true)
+class GenericBoundary<T extends num> {
+  T? _genericValue;
+}
+
+@ForyStruct(exposePrivateFields: true)
+mixin SecretMixin {
+  int _mixedValue = 0;
+}
+'''),
+      );
+
+      _expectOnlyVisibilityBridge(output, const <String>[
+        'AbstractBoundary',
+        'GenericBoundary',
+        'SecretMixin',
+      ]);
+      expect(
+        output,
+        matches(
+          RegExp(
+            r'static T\? \$g[0-9a-f]{16}<T extends num>\('
+            r'GenericBoundary<T> value\)',
+          ),
+        ),
+      );
+      expect(output, contains('GenericBoundary<T> value'));
+      expect(output, contains('SecretMixin value'));
+    });
+
+    test('concrete provider owns both serializer and companion', () async {
+      const inputPath = 'test/concrete_provider.dart';
+      final output = await _generate(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+class ConcreteProvider {
+  int _secret = 0;
+  int visible = 1;
+}
+'''),
+      );
+
+      expect(
+        output,
+        contains(r'abstract final class $ConcreteProviderForyFieldAccess'),
+      );
+      expect(output, contains('final class _ConcreteProviderForySerializer'));
+      expect(output, contains('GeneratedStructSchema<ConcreteProvider>'));
+      expect(output, contains('ConcreteProviderForyModule'));
+    });
+
+    test('concrete exposure leaves its serializer surface unchanged', () async {
+      const inputPath = 'test/concrete_surface.dart';
+      final normal = await _generate(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct()
+class ConcreteSurface {
+  int _value = 0;
+}
+'''),
+      );
+      final exposed = await _generate(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+class ConcreteSurface {
+  int _value = 0;
+}
+'''),
+      );
+      const marker =
+          'const List<GeneratedFieldInfo> _concreteSurfaceForyFieldInfo';
+
+      expect(
+        exposed.substring(exposed.indexOf(marker)),
+        normal.substring(normal.indexOf(marker)),
+      );
+      expect(exposed, contains(r'class $ConcreteSurfaceForyFieldAccess'));
+      expect(normal, isNot(contains(r'$ConcreteSurfaceForyFieldAccess')));
+    });
+
+    test('exposePrivateFields defaults to false', () async {
+      const inputPath = 'test/default_false.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct()
+abstract class Boundary {
+  int _secret = 0;
+}
+'''),
+        message: 'does not own a concrete generated serializer',
+      );
+    });
+
+    test('consumer annotation cannot authorize parent storage', () async {
+      const inputPath = 'test/consumer_annotation.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+class Child extends Boundary {
+  int _childSecret = 0;
+}
+''', directives: "import 'unexposed_parent.dart';"),
+        additionalAssets: <String, String>{
+          'fory|test/unexposed_parent.dart': '''
+abstract class Boundary {
+  int _parentSecret = 0;
+}
+''',
+        },
+        message:
+            'No public hierarchy boundary in the declaring library is '
+            'annotated with @ForyStruct(exposePrivateFields: true)',
+      );
+    });
+
+    test('external target cannot expose private fields', () async {
+      const inputPath = 'test/external_exposure.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+class Target {
+  int value = 0;
+}
+
+@ForyStruct(target: Target, exposePrivateFields: true)
+abstract final class TargetSchema {
+  late final int value;
+}
+'''),
+        message:
+            'ForyStruct.exposePrivateFields cannot be used with '
+            'ForyStruct.target',
+      );
+    });
+
+    test('empty or fully ignored provider is rejected', () async {
+      const inputPath = 'test/empty_provider.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  int visible = 0;
+
+  @ForyField(ignore: true)
+  int _ignored = 0;
+}
+'''),
+        message: 'has no non-ignored private storage',
+      );
+    });
+
+    test('provider boundary must be public and externally usable', () async {
+      const cases = <({String name, String declaration, String message})>[
+        (
+          name: 'private',
+          declaration: '''
+@ForyStruct(exposePrivateFields: true)
+abstract class _Boundary {
+  int _secret = 0;
+}
+''',
+          message: 'requires a public hierarchy boundary',
+        ),
+        (
+          name: 'final',
+          declaration: '''
+@ForyStruct(exposePrivateFields: true)
+final class Boundary {
+  int _secret = 0;
+}
+''',
+          message: 'cannot be extended or mixed in outside',
+        ),
+      ];
+
+      for (final testCase in cases) {
+        final inputPath = 'test/${testCase.name}_provider.dart';
+        await _expectGenerationError(
+          inputPath: inputPath,
+          source: _librarySource(inputPath, testCase.declaration),
+          message: testCase.message,
+        );
+      }
+    });
+
+    test('provider-only boundary rejects schema options', () async {
+      const cases = <({String name, String annotation, String message})>[
+        (
+          name: 'evolving',
+          annotation: '@ForyStruct(exposePrivateFields: true, evolving: false)',
+          message: 'ForyStruct.evolving has no meaning',
+        ),
+        (
+          name: 'constructor',
+          annotation:
+              "@ForyStruct(exposePrivateFields: true, constructor: 'named')",
+          message: 'ForyStruct.constructor has no meaning',
+        ),
+      ];
+
+      for (final testCase in cases) {
+        final inputPath = 'test/${testCase.name}_provider_option.dart';
+        await _expectGenerationError(
+          inputPath: inputPath,
+          source: _librarySource(inputPath, '''
+${testCase.annotation}
+abstract class Boundary {
+  int _secret = 0;
+}
+'''),
+          message: testCase.message,
+        );
+      }
+    });
+  });
+
+  group('companion API', () {
+    test('emits mutable accessors and getter-only final access', () async {
+      const inputPath = 'test/accessor_shape.dart';
+      final output = await _generate(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  int _mutable = 0;
+  final int _immutable = 1;
+}
+'''),
+      );
+
+      _expectOnlyVisibilityBridge(output, const <String>['Boundary']);
+      expect(
+        RegExp(
+          r'static int \$g[0-9a-f]{16}\(Boundary value\)',
+        ).allMatches(output),
+        hasLength(2),
+      );
+      expect(
+        RegExp(r'static void \$s[0-9a-f]{16}\(').allMatches(output),
+        hasLength(1),
+      );
+      expect(output, contains('=> value._mutable;'));
+      expect(output, contains('value._mutable = fieldValue;'));
+      expect(output, contains('=> value._immutable;'));
+      expect(output, isNot(contains('value._immutable = fieldValue;')));
+    });
+
+    test('public boundary exposes a private ancestor slot', () async {
+      const providerPath = 'test/private_ancestor_provider.dart';
+      final providerSource = _librarySource(providerPath, '''
+class _PrivateStorage {
+  int _secret = 0;
+}
+
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary extends _PrivateStorage {}
+''');
+      final providerOutput = await _generate(
+        inputPath: providerPath,
+        source: providerSource,
+      );
+
+      expect(
+        providerOutput,
+        contains(r'abstract final class $BoundaryForyFieldAccess'),
+      );
+      expect(providerOutput, contains('Boundary value'));
+      expect(providerOutput, contains('=> value._secret;'));
+      expect(providerOutput, contains('value._secret = fieldValue;'));
+      expect(
+        providerOutput,
+        isNot(contains(r'$_PrivateStorageForyFieldAccess')),
+      );
+      final getterName =
+          RegExp(
+            r'static int (\$g[0-9a-f]{16})\(Boundary value\)',
+          ).firstMatch(providerOutput)!.group(1)!;
+      final setterName =
+          RegExp(
+            r'static void (\$s[0-9a-f]{16})\(',
+          ).firstMatch(providerOutput)!.group(1)!;
+
+      const childPath = 'test/private_ancestor_child.dart';
+      final childOutput = await _generate(
+        inputPath: childPath,
+        source: _librarySource(childPath, '''
+@ForyStruct()
+class Child extends Boundary {
+  int local = 0;
+}
+''', directives: "import 'private_ancestor_provider.dart';"),
+        additionalAssets: <String, String>{
+          'fory|$providerPath': providerSource,
+        },
+      );
+
+      expect(childOutput, contains(r'$BoundaryForyFieldAccess.' + getterName));
+      expect(childOutput, contains(r'$BoundaryForyFieldAccess.' + setterName));
+      expect(childOutput, isNot(contains('value._secret')));
+      expect(childOutput, isNot(contains('_BoundaryForySerializer')));
+    });
+
+    test('uses one companion for each declaring library', () async {
+      const aPath = 'test/library_a.dart';
+      const bPath = 'test/library_b.dart';
+      const childPath = 'test/multi_library_child.dart';
+      final aSource = _librarySource(aPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class ABoundary {
+  int _a = 0;
+}
+''');
+      final bSource = _librarySource(
+        bPath,
+        '''
+@ForyStruct(exposePrivateFields: true)
+abstract class BBoundary extends ABoundary {
+  int _b = 0;
+}
+''',
+        directives: '''
+import 'library_a.dart';
+export 'library_a.dart';
+''',
+      );
+      final output = await _generate(
+        inputPath: childPath,
+        source: _librarySource(childPath, '''
+@ForyStruct()
+class Child extends BBoundary {}
+''', directives: "import 'library_b.dart';"),
+        additionalAssets: <String, String>{
+          'fory|$aPath': aSource,
+          'fory|$bPath': bSource,
+        },
+      );
+
+      expect(output, contains(r'$ABoundaryForyFieldAccess.$g'));
+      expect(output, contains(r'$ABoundaryForyFieldAccess.$s'));
+      expect(output, contains(r'$BBoundaryForyFieldAccess.$g'));
+      expect(output, contains(r'$BBoundaryForyFieldAccess.$s'));
+      expect(output, isNot(contains('_ABoundaryForySerializer')));
+      expect(output, isNot(contains('_BBoundaryForySerializer')));
+    });
+
+    test('uses stable distinct getter and setter digests', () async {
+      const inputPath = 'test/digest_provider.dart';
+      final source = _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  int _first = 0;
+  int _second = 0;
+}
+''');
+      final first = await _generate(inputPath: inputPath, source: source);
+      final second = await _generate(inputPath: inputPath, source: source);
+
+      expect(second, first);
+      final getterDigests =
+          RegExp(
+            r'static int \$g([0-9a-f]{16})\(',
+          ).allMatches(first).map((match) => match.group(1)!).toSet();
+      final setterDigests =
+          RegExp(
+            r'static void \$s([0-9a-f]{16})\(',
+          ).allMatches(first).map((match) => match.group(1)!).toSet();
+      expect(getterDigests, hasLength(2));
+      expect(setterDigests, getterDigests);
+    });
+  });
+
+  group('consumer resolution', () {
+    final providerSource = _providerSource();
+
+    test('resolves direct, prefixed, re-exported, and shown helpers', () async {
+      final cases = <
+        ({
+          String name,
+          String directives,
+          Map<String, String> assets,
+          String parent,
+          String expected,
+        })
+      >[
+        (
+          name: 'direct',
+          directives: "import 'private_provider.dart';",
+          assets: const <String, String>{},
+          parent: 'Boundary',
+          expected: r'$BoundaryForyFieldAccess.$g',
+        ),
+        (
+          name: 'prefixed',
+          directives: "import 'private_provider.dart' as p;",
+          assets: const <String, String>{},
+          parent: 'p.Boundary',
+          expected: r'p.$BoundaryForyFieldAccess.$g',
+        ),
+        (
+          name: 'reexported',
+          directives: "import 'private_barrel.dart';",
+          assets: const <String, String>{
+            'fory|test/private_barrel.dart': "export 'private_provider.dart';",
+          },
+          parent: 'Boundary',
+          expected: r'$BoundaryForyFieldAccess.$g',
+        ),
+        (
+          name: 'shown',
+          directives:
+              r"import 'private_provider.dart' "
+              r'show Boundary, $BoundaryForyFieldAccess;',
+          assets: const <String, String>{},
+          parent: 'Boundary',
+          expected: r'$BoundaryForyFieldAccess.$g',
+        ),
+        (
+          name: 'split_unprefixed',
+          directives:
+              "import 'private_provider.dart' show Boundary;\n"
+              r"import 'private_provider.dart' "
+              r'show $BoundaryForyFieldAccess;',
+          assets: const <String, String>{},
+          parent: 'Boundary',
+          expected: r'$BoundaryForyFieldAccess.$g',
+        ),
+        (
+          name: 'split_prefixed',
+          directives:
+              "import 'private_provider.dart' as p show Boundary;\n"
+              r"import 'private_provider.dart' as p "
+              r'show $BoundaryForyFieldAccess;',
+          assets: const <String, String>{},
+          parent: 'p.Boundary',
+          expected: r'p.$BoundaryForyFieldAccess.$g',
+        ),
+      ];
+
+      for (final testCase in cases) {
+        final inputPath = 'test/${testCase.name}_consumer.dart';
+        final output = await _generate(
+          inputPath: inputPath,
+          source: _librarySource(inputPath, '''
+@ForyStruct()
+class Child extends ${testCase.parent} {}
+''', directives: testCase.directives),
+          additionalAssets: <String, String>{
+            'fory|test/private_provider.dart': providerSource,
+            ...testCase.assets,
+          },
+        );
+
+        expect(output, contains(testCase.expected));
+        expect(
+          output,
+          contains(testCase.expected.replaceFirst(r'.$g', r'.$s')),
+        );
+        expect(output, isNot(contains('_BoundaryForySerializer')));
+      }
+    });
+
+    test('rejects show, hide, and re-export paths missing helper', () async {
+      final cases =
+          <({String name, String directives, Map<String, String> assets})>[
+            (
+              name: 'show_missing',
+              directives: "import 'private_provider.dart' show Boundary;",
+              assets: const <String, String>{},
+            ),
+            (
+              name: 'hide_helper',
+              directives:
+                  r"import 'private_provider.dart' "
+                  r'hide $BoundaryForyFieldAccess;',
+              assets: const <String, String>{},
+            ),
+            (
+              name: 'reexport_missing',
+              directives: "import 'private_hidden_barrel.dart';",
+              assets: const <String, String>{
+                'fory|test/private_hidden_barrel.dart':
+                    "export 'private_provider.dart' show Boundary;",
+              },
+            ),
+          ];
+
+      for (final testCase in cases) {
+        final inputPath = 'test/${testCase.name}_consumer.dart';
+        await _expectGenerationError(
+          inputPath: inputPath,
+          source: _librarySource(inputPath, '''
+@ForyStruct()
+class Child extends Boundary {}
+''', directives: testCase.directives),
+          additionalAssets: <String, String>{
+            'fory|test/private_provider.dart': providerSource,
+            ...testCase.assets,
+          },
+          message:
+              'generated companion is not visible through the child library '
+              'imports and re-exports',
+        );
+      }
+    });
+
+    test('rejects an ambiguous companion import namespace', () async {
+      const inputPath = 'test/ambiguous_consumer.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct()
+class Child extends Boundary {}
+''', directives: "import 'ambiguous_barrel.dart';"),
+        additionalAssets: <String, String>{
+          'fory|test/private_provider.dart': providerSource,
+          'fory|test/conflicting_helper.dart': r'''
+abstract final class $BoundaryForyFieldAccess {}
+''',
+          'fory|test/ambiguous_barrel.dart': '''
+export 'private_provider.dart';
+export 'conflicting_helper.dart';
+''',
+        },
+        message: 'is ambiguous in import namespace',
+      );
+    });
+
+    test('uses a valid prefix when another prefix is ambiguous', () async {
+      const inputPath = 'test/valid_and_ambiguous_consumer.dart';
+      final output = await _generate(
+        inputPath: inputPath,
+        source: _librarySource(
+          inputPath,
+          '''
+@ForyStruct()
+class Child extends p.Boundary {}
+''',
+          directives: '''
+import 'private_provider.dart' as p;
+import 'ambiguous_barrel.dart' as q;
+''',
+        ),
+        additionalAssets: <String, String>{
+          'fory|test/private_provider.dart': providerSource,
+          'fory|test/conflicting_helper.dart': r'''
+abstract final class $BoundaryForyFieldAccess {}
+''',
+          'fory|test/ambiguous_barrel.dart': '''
+export 'private_provider.dart';
+export 'conflicting_helper.dart';
+''',
+        },
+      );
+
+      expect(output, contains(r'p.$BoundaryForyFieldAccess.$g'));
+      expect(output, isNot(contains(r'q.$BoundaryForyFieldAccess.$g')));
+    });
+
+    test('rejects a local value shadowing the companion', () async {
+      const inputPath = 'test/local_shadow_consumer.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, r'''
+Object get $BoundaryForyFieldAccess => Object();
+
+@ForyStruct()
+class Child extends Boundary {}
+''', directives: "import 'private_provider.dart';"),
+        additionalAssets: <String, String>{
+          'fory|test/private_provider.dart': providerSource,
+        },
+        message: 'collides with',
+      );
+    });
+
+    test(
+      'rejects an import prefix shadowing the unprefixed companion',
+      () async {
+        const inputPath = 'test/prefix_shadow_consumer.dart';
+        await _expectGenerationError(
+          inputPath: inputPath,
+          source: _librarySource(
+            inputPath,
+            '''
+@ForyStruct()
+class Child extends Boundary {}
+''',
+            directives: r'''
+import 'private_provider.dart';
+import 'unrelated.dart' as $BoundaryForyFieldAccess;
+''',
+          ),
+          additionalAssets: <String, String>{
+            'fory|test/private_provider.dart': providerSource,
+            'fory|test/unrelated.dart': 'class Unrelated {}',
+          },
+          message: 'collides with',
+        );
+      },
+    );
+
+    test(
+      'uses a prefix when a local value shadows the unprefixed name',
+      () async {
+        const inputPath = 'test/local_shadow_prefixed_consumer.dart';
+        final output = await _generate(
+          inputPath: inputPath,
+          source: _librarySource(inputPath, r'''
+Object get $BoundaryForyFieldAccess => Object();
+
+@ForyStruct()
+class Child extends p.Boundary {}
+''', directives: "import 'private_provider.dart' as p;"),
+          additionalAssets: <String, String>{
+            'fory|test/private_provider.dart': providerSource,
+          },
+        );
+
+        expect(output, contains(r'p.$BoundaryForyFieldAccess.$g'));
+      },
+    );
+
+    test('rejects a locally shadowed boundary namespace', () async {
+      const providerPath = 'test/shadowed_boundary_provider.dart';
+      const middlePath = 'test/shadowed_boundary_middle.dart';
+      const childPath = 'test/shadowed_boundary_child.dart';
+      final provider = _providerSource(inputPath: providerPath);
+      await _expectGenerationError(
+        inputPath: childPath,
+        source: _librarySource(childPath, '''
+class Boundary {}
+
+@ForyStruct()
+class Child extends PublicSubclass {}
+''', directives: "import 'shadowed_boundary_middle.dart';"),
+        additionalAssets: <String, String>{
+          'fory|$providerPath': provider,
+          'fory|$middlePath': '''
+import 'shadowed_boundary_provider.dart';
+export 'shadowed_boundary_provider.dart';
+
+class PublicSubclass extends Boundary {}
+''',
+        },
+        message:
+            'generated companion is not visible through the child library '
+            'imports and re-exports',
+      );
+    });
+
+    test('uses a public alias without importing its underlying type', () async {
+      const providerPath = 'test/public_alias_provider.dart';
+      const childPath = 'test/public_alias_consumer.dart';
+      final provider = _librarySource(providerPath, '''
+typedef PublicAlias = PublicValue;
+
+@ForyStruct(exposePrivateFields: true)
+abstract class AliasBoundary {
+  PublicAlias? _value;
+}
+''', directives: "import 'public_alias_value.dart';");
+      final output = await _generate(
+        inputPath: childPath,
+        source: _librarySource(childPath, '''
+@ForyStruct()
+class AliasChild extends AliasBoundary {}
+''', directives: "import 'public_alias_provider.dart';"),
+        additionalAssets: <String, String>{
+          'fory|$providerPath': provider,
+          'fory|test/public_alias_value.dart': 'class PublicValue {}',
+        },
+      );
+
+      expect(output, contains('final PublicAlias? __valueValue'));
+      expect(output, contains(r'$AliasBoundaryForyFieldAccess.$g'));
+    });
+  });
+
+  group('public signature validation', () {
+    test('rejects a private nominal field type', () async {
+      const inputPath = 'test/private_nominal.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+class _Hidden {}
+
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  _Hidden? _value;
+}
+'''),
+        message: 'would expose private nominal type _Hidden',
+      );
+    });
+
+    test('rejects a nested private nominal field type', () async {
+      const inputPath = 'test/nested_private_nominal.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+class _Hidden {}
+
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  List<_Hidden> _values = <_Hidden>[];
+}
+'''),
+        message: 'would expose private nominal type _Hidden',
+      );
+    });
+
+    test('rejects a private generic bound', () async {
+      const inputPath = 'test/private_bound.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+class _Hidden {}
+
+@ForyStruct(exposePrivateFields: true)
+class Boundary<T extends _Hidden> {
+  T? _value;
+}
+'''),
+        message: 'would expose private nominal type _Hidden',
+      );
+    });
+
+    test('expands a private nonnominal alias to its public type', () async {
+      const inputPath = 'test/private_alias.dart';
+      final output = await _generate(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+typedef _Count = int;
+
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  _Count _count = 0;
+}
+'''),
+      );
+
+      expect(
+        output,
+        matches(RegExp(r'static int \$g[0-9a-f]{16}\(Boundary value\)')),
+      );
+      expect(output, isNot(contains('static _Count ')));
+    });
+
+    test('rejects record signatures unsupported by ordinary structs', () async {
+      const inputPath = 'test/record_signature.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  (int,) _value = (0,);
+}
+'''),
+        message: 'ordinary ForyStruct fields do not support that type',
+      );
+    });
+
+    test('rejects nominal Function signatures', () async {
+      const inputPath = 'test/function_signature.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  Function _callback = () {};
+}
+'''),
+        message: 'Companion signatures must be exact, public, and non-callback',
+      );
+    });
+
+    test('rejects Null signatures unsupported by ordinary structs', () async {
+      const inputPath = 'test/null_signature.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  Null _value;
+}
+'''),
+        message: 'ordinary ForyStruct fields do not support that type',
+      );
+    });
+
+    test('rejects callback signatures instead of adding a carrier', () async {
+      const inputPath = 'test/private_callback.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, '''
+typedef _Callback = int Function(int);
+
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  _Callback _callback = _identity;
+}
+
+int _identity(int value) => value;
+'''),
+        message: 'Companion signatures must be exact, public, and non-callback',
+      );
+    });
+  });
+
+  group('exact storage and naming', () {
+    test('rejects hiding after the authorized boundary', () async {
+      const providerPath = 'test/hiding_provider.dart';
+      const childPath = 'test/hiding_child.dart';
+      final providerSource = _librarySource(providerPath, '''
+class _Storage {
+  int _secret = 0;
+}
+
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary extends _Storage {}
+
+class LaterBoundary extends Boundary {
+  @override
+  int get _secret => 1;
+
+  @override
+  set _secret(int value) {}
+}
+''');
+      await _expectGenerationError(
+        inputPath: childPath,
+        source: _librarySource(childPath, '''
+@ForyStruct()
+class Child extends LaterBoundary {}
+''', directives: "import 'hiding_provider.dart';"),
+        additionalAssets: <String, String>{
+          'fory|$providerPath': providerSource,
+        },
+        message: 'does not expose that exact slot through its effective getter',
+      );
+    });
+
+    test('rejects an ignored hider after the authorized boundary', () async {
+      const providerPath = 'test/ignored_hiding_provider.dart';
+      const childPath = 'test/ignored_hiding_child.dart';
+      final providerSource = _librarySource(providerPath, '''
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  int _secret = 0;
+}
+
+class LaterBoundary extends Boundary {
+  @ForyField(ignore: true)
+  int _secret = 1;
+}
+''');
+      await _expectGenerationError(
+        inputPath: childPath,
+        source: _librarySource(childPath, '''
+@ForyStruct()
+class Child extends LaterBoundary {}
+''', directives: "import 'ignored_hiding_provider.dart';"),
+        additionalAssets: <String, String>{
+          'fory|$providerPath': providerSource,
+        },
+        message: 'does not expose that exact slot through its effective getter',
+      );
+    });
+
+    test('rejects a user declaration colliding with helper name', () async {
+      const inputPath = 'test/helper_collision.dart';
+      await _expectGenerationError(
+        inputPath: inputPath,
+        source: _librarySource(inputPath, r'''
+abstract final class $BoundaryForyFieldAccess {}
+
+@ForyStruct(exposePrivateFields: true)
+abstract class Boundary {
+  int _secret = 0;
+}
+'''),
+        message: 'collides with an existing top-level declaration',
+      );
+    });
+  });
+}
diff --git a/dart/packages/fory/test/xlang_protocol_test.dart b/dart/packages/fory/test/xlang_protocol_test.dart
index 3251c3a30..e33992445 100644
--- a/dart/packages/fory/test/xlang_protocol_test.dart
+++ b/dart/packages/fory/test/xlang_protocol_test.dart
@@ -53,6 +53,24 @@ final class _SchemaRemoteB {}
 
 final class _SchemaRemoteC {}
 
+final class _DuplicateIdSchema {}
+
+final class _DuplicateNameSchema {}
+
+final class _InconsistentTagSchema {}
+
+final class _IdentityDomainLocal {}
+
+final class _IdentityDomainRemote {}
+
+final class _RemoteDuplicateIdLocal {}
+
+final class _RemoteDuplicateIdWriter {}
+
+final class _RemoteDuplicateNameLocal {}
+
+final class _RemoteDuplicateNameWriter {}
+
 final class _LateDartExt {}
 
 final class _LateDartHolder {}
@@ -109,6 +127,13 @@ GeneratedFieldInfo _generatedField(String name) => GeneratedFieldInfo(
   fieldType: _intFieldType,
 );
 
+GeneratedFieldInfo _taggedField(int id) => GeneratedFieldInfo(
+  name: 'tagged$id',
+  identifier: '$id',
+  id: id,
+  fieldType: _intFieldType,
+);
+
 GeneratedFieldInfo _generatedMapField(String name) => GeneratedFieldInfo(
   name: name,
   identifier: name,
@@ -258,6 +283,92 @@ void _readTypeMeta(TypeResolver resolver, Uint8List bytes) {
   );
 }
 
+Uint8List _rewriteTypeDefBody(
+  Uint8List typeMetaBytes,
+  void Function(Uint8List body) rewrite,
+) {
+  final source = Buffer.wrap(typeMetaBytes);
+  final typeId = source.readVarUint32Small7();
+  final marker = source.readVarUint32Small14();
+  if (marker != 0) {
+    throw StateError('Expected an inline TypeDef.');
+  }
+  final header = TypeHeader(source.readInt64());
+  final bodyLength = header.readMetaSize(source);
+  final body = Uint8List.fromList(source.readBytes(bodyLength));
+  if (source.readableBytes != 0) {
+    throw StateError('Expected one complete TypeDef.');
+  }
+  rewrite(body);
+
+  final result = Buffer();
+  result.writeVarUint32Small7(typeId);
+  result.writeVarUint32(marker);
+  result.writeInt64(typeDefHeader(body));
+  if (body.length >= 0xff) {
+    result.writeVarUint32(body.length - 0xff);
+  }
+  result.writeBytes(body);
+  return result.toBytes();
+}
+
+void _replaceUniqueBytes(
+  Uint8List bytes,
+  List<int> source,
+  List<int> replacement,
+) {
+  if (source.length != replacement.length) {
+    throw ArgumentError('Replacement byte lengths must match.');
+  }
+  var match = -1;
+  for (var offset = 0; offset <= bytes.length - source.length; offset += 1) {
+    var equal = true;
+    for (var index = 0; index < source.length; index += 1) {
+      if (bytes[offset + index] != source[index]) {
+        equal = false;
+        break;
+      }
+    }
+    if (!equal) {
+      continue;
+    }
+    if (match >= 0) {
+      throw StateError('Expected a unique byte sequence.');
+    }
+    match = offset;
+  }
+  if (match < 0) {
+    throw StateError('Byte sequence was not found.');
+  }
+  bytes.setRange(match, match + replacement.length, replacement);
+}
+
+Uint8List _duplicateTaggedTypeDef(Uint8List validBytes) {
+  const tagOneHeader = (3 << 6) | (1 << 2);
+  const tagTwoHeader = (3 << 6) | (2 << 2);
+  return _rewriteTypeDefBody(
+    validBytes,
+    (body) => _replaceUniqueBytes(
+      body,
+      const <int>[tagTwoHeader, TypeIds.int32],
+      const <int>[tagOneHeader, TypeIds.int32],
+    ),
+  );
+}
+
+Uint8List _duplicateNamedTypeDef(Uint8List validBytes) {
+  final first = encodeFieldNameMetaString('alpha');
+  final second = encodeFieldNameMetaString('bravo');
+  if (first.encoding != second.encoding ||
+      first.bytes.length != second.bytes.length) {
+    throw StateError('Test field names must use the same encoding shape.');
+  }
+  return _rewriteTypeDefBody(
+    validBytes,
+    (body) => _replaceUniqueBytes(body, second.bytes, first.bytes),
+  );
+}
+
 void main() {
   group('xlang protocol regressions', () {
     test('deserializes NONE wire values as null', () {
@@ -389,6 +500,173 @@ void main() {
       );
     });
 
+    test('rejects duplicate local field ids', () {
+      final resolver = TypeResolver(Config());
+      _rememberSchema(_DuplicateIdSchema, <GeneratedFieldInfo>[
+        _taggedField(1),
+        _taggedField(1),
+      ]);
+
+      expect(
+        () => resolver.registerGenerated(
+          _DuplicateIdSchema,
+          namespace: 'example',
+          typeName: 'DuplicateId',
+        ),
+        throwsA(
+          isA<ArgumentError>().having(
+            (error) => error.toString(),
+            'message',
+            contains('Duplicate field id 1'),
+          ),
+        ),
+      );
+    });
+
+    test('rejects duplicate local field names', () {
+      final resolver = TypeResolver(Config());
+      _rememberSchema(_DuplicateNameSchema, <GeneratedFieldInfo>[
+        _generatedField('value'),
+        _generatedMapField('value'),
+      ]);
+
+      expect(
+        () => resolver.registerGenerated(
+          _DuplicateNameSchema,
+          namespace: 'example',
+          typeName: 'DuplicateName',
+        ),
+        throwsA(
+          isA<ArgumentError>().having(
+            (error) => error.toString(),
+            'message',
+            contains('Duplicate field wire name value'),
+          ),
+        ),
+      );
+    });
+
+    test('rejects inconsistent local tagged identity', () {
+      final resolver = TypeResolver(Config());
+      _rememberSchema(_InconsistentTagSchema, <GeneratedFieldInfo>[
+        const GeneratedFieldInfo(
+          name: 'tagged',
+          identifier: '2',
+          id: 1,
+          fieldType: _intFieldType,
+        ),
+      ]);
+
+      expect(
+        () => resolver.registerGenerated(
+          _InconsistentTagSchema,
+          namespace: 'example',
+          typeName: 'InconsistentTag',
+        ),
+        throwsA(
+          isA<ArgumentError>().having(
+            (error) => error.toString(),
+            'message',
+            contains('textual identifier 2, which must match field id 1'),
+          ),
+        ),
+      );
+    });
+
+    test('keeps tagged ids separate from field names', () {
+      const name = 'example.IdentityDomains';
+      final reader = TypeResolver(Config());
+      _rememberSchema(_IdentityDomainLocal, <GeneratedFieldInfo>[
+        _taggedField(1),
+        _generatedMapField('1'),
+      ]);
+      reader.registerGenerated(
+        _IdentityDomainLocal,
+        namespace: 'example',
+        typeName: 'IdentityDomains',
+      );
+      final remote = _typeMetaBytes(
+        _IdentityDomainRemote,
+        name,
+        <GeneratedFieldInfo>[_generatedMapField('1'), _taggedField(1)],
+      );
+
+      _readTypeMeta(reader, remote);
+    });
+
+    test('rejects duplicate remote field ids before caching', () {
+      const name = 'example.RemoteDuplicateId';
+      final reader = TypeResolver(Config(maxSchemaVersionsPerType: 1));
+      _rememberSchema(_RemoteDuplicateIdLocal, <GeneratedFieldInfo>[
+        _generatedField('value'),
+      ]);
+      reader.registerGenerated(
+        _RemoteDuplicateIdLocal,
+        namespace: 'example',
+        typeName: 'RemoteDuplicateId',
+      );
+      final valid = _typeMetaBytes(
+        _RemoteDuplicateIdWriter,
+        name,
+        <GeneratedFieldInfo>[_taggedField(1), _taggedField(2)],
+      );
+      final duplicate = _duplicateTaggedTypeDef(valid);
+
+      expect(
+        () => _readTypeMeta(reader, duplicate),
+        throwsA(
+          isA<StateError>().having(
+            (error) => error.toString(),
+            'message',
+            contains('Duplicate field id 1'),
+          ),
+        ),
+      );
+      expect(
+        () => _readTypeMeta(reader, duplicate),
+        throwsA(isA<StateError>()),
+      );
+      _readTypeMeta(reader, valid);
+    });
+
+    test('rejects duplicate remote field names before caching', () {
+      const name = 'example.RemoteDuplicateName';
+      final reader = TypeResolver(Config(maxSchemaVersionsPerType: 1));
+      _rememberSchema(_RemoteDuplicateNameLocal, <GeneratedFieldInfo>[
+        _generatedField('value'),
+      ]);
+      reader.registerGenerated(
+        _RemoteDuplicateNameLocal,
+        namespace: 'example',
+        typeName: 'RemoteDuplicateName',
+      );
+      final valid = _typeMetaBytes(
+        _RemoteDuplicateNameWriter,
+        name,
+        <GeneratedFieldInfo>[
+          _generatedField('alpha'),
+          _generatedField('bravo'),
+        ],
+      );
+      final duplicate = _duplicateNamedTypeDef(valid);
+
+      expect(
+        () => _readTypeMeta(reader, duplicate),
+        throwsA(
+          isA<StateError>().having(
+            (error) => error.toString(),
+            'message',
+            contains('Duplicate field wire name alpha'),
+          ),
+        ),
+      );
+      expect(
+        () => _readTypeMeta(reader, duplicate),
+        throwsA(isA<StateError>()),
+      );
+      _readTypeMeta(reader, valid);
+    });
+
     test('remote schema limit rejects extra versions', () {
       const name = 'example.Unknown';
       final reader = TypeResolver(Config(maxSchemaVersionsPerType: 1));
diff --git a/dart/packages/fory-test/pubspec.yaml b/dart/packages/inheritance-test-models/analysis_options.yaml
similarity index 71%
copy from dart/packages/fory-test/pubspec.yaml
copy to dart/packages/inheritance-test-models/analysis_options.yaml
index 204c72226..8ecc37147 100644
--- a/dart/packages/fory-test/pubspec.yaml
+++ b/dart/packages/inheritance-test-models/analysis_options.yaml
@@ -15,21 +15,9 @@
 # specific language governing permissions and limitations
 # under the License.
 
-name: fory_test
-description: Apache Fory Dart consumer and integration tests
-version: 1.5.0-dev
+include: package:lints/recommended.yaml
 
-resolution: workspace
-
-environment:
-  sdk: ^3.7.0
-
-dependencies:
-  external_type_test_models: 1.0.0
-  fory: 1.5.0-dev
-
-dev_dependencies:
-  build_runner: '>=2.7.0 <3.0.0'
-  lints: ^6.1.0
-  test: ^1.31.1
-  vm_service: ^15.0.0
+analyzer:
+  exclude:
+    - .dart_tool/**
+    - lib/**/*.fory.dart
diff --git a/dart/packages/inheritance-test-models/lib/base_models.dart b/dart/packages/inheritance-test-models/lib/base_models.dart
new file mode 100644
index 000000000..2cc70ec57
--- /dev/null
+++ b/dart/packages/inheritance-test-models/lib/base_models.dart
@@ -0,0 +1,92 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+library;
+
+import 'package:fory/fory.dart';
+
+part 'base_models.fory.dart';
+
+class _PrivateGenericStorage<T> {
+  _PrivateGenericStorage(T storedValue) : _ownerValue = storedValue;
+
+  @ForyField(id: 1)
+  final T _ownerValue;
+
+  @ForyField(id: 2)
+  String _sharedName = 'base-default';
+
+  @ForyField(id: 4)
+  String publicLabel = '';
+
+  T get ownerValue => _ownerValue;
+
+  String get basePrivateName => _sharedName;
+
+  void setBasePrivateName(String value) {
+    _sharedName = value;
+  }
+}
+
+@ForyStruct(exposePrivateFields: true)
+abstract class PublicGenericBoundary<T> extends _PrivateGenericStorage<T> {
+  // Keep explicit forwarding to exercise constructor identity analysis.
+  // ignore: use_super_parameters
+  PublicGenericBoundary(T value) : super(value);
+}
+
+@ForyStruct(exposePrivateFields: true)
+mixin PublicPrivateMixin {
+  @ForyField(id: 3, type: Int32Type())
+  int _mixinCount = 0;
+
+  int get mixinCount => _mixinCount;
+
+  void setMixinCount(int value) {
+    _mixinCount = value;
+  }
+}
+
+@ForyStruct(exposePrivateFields: true)
+abstract class XlangInheritanceBoundary {
+  @ForyField(id: 2, type: Int32Type())
+  int _parentInt = 0;
+
+  @ForyField(id: 6)
+  bool _parentFlag = false;
+
+  @ForyField(id: 10)
+  String _parentText = '';
+
+  int get parentInt => _parentInt;
+
+  bool get parentFlag => _parentFlag;
+
+  String get parentText => _parentText;
+
+  void setParentValues({
+    required int parentInt,
+    required bool parentFlag,
+    required String parentText,
+  }) {
+    _parentInt = parentInt;
+    _parentFlag = parentFlag;
+    _parentText = parentText;
+  }
+}
diff --git a/dart/packages/inheritance-test-models/lib/middle_models.dart b/dart/packages/inheritance-test-models/lib/middle_models.dart
new file mode 100644
index 000000000..66f3c9019
--- /dev/null
+++ b/dart/packages/inheritance-test-models/lib/middle_models.dart
@@ -0,0 +1,52 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements.  See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership.  The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License.  You may obtain a copy of the License at
+ *
+ *   http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied.  See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ */
+
+library;
+
+import 'package:fory/fory.dart';
+import 'package:inheritance_test_models/base_models.dart';
+
+part 'middle_models.fory.dart';
+
+@ForyStruct(exposePrivateFields: true)
+abstract class PublicMiddleBoundary extends PublicGenericBoundary<String>
+    with PublicPrivateMixin {
+  // Keep explicit forwarding to exercise constructor identity analysis.
+  // ignore: use_super_parameters
+  PublicMiddleBoundary(String baseValue, int middleValue)
+    : _middleValue = middleValue,
+      super(baseValue);
+
+  @ForyField(id: 11, type: Int32Type())
+  final int _middleValue;
+
+  @ForyField(id: 12)
+  String _sharedName = 'middle-default';
+
+  @ForyField(id: 14, type: Int32Type())
+  int publicMiddleCount = 0;
+
+  int get middleValue => _middleValue;
+
+  String get middlePrivateName => _sharedName;
+
+  void setMiddlePrivateName(String value) {
+    _sharedName = value;
+  }
+}
diff --git a/dart/packages/fory-test/pubspec.yaml b/dart/packages/inheritance-test-models/pubspec.yaml
similarity index 80%
copy from dart/packages/fory-test/pubspec.yaml
copy to dart/packages/inheritance-test-models/pubspec.yaml
index 204c72226..bcf8be7e0 100644
--- a/dart/packages/fory-test/pubspec.yaml
+++ b/dart/packages/inheritance-test-models/pubspec.yaml
@@ -15,9 +15,10 @@
 # specific language governing permissions and limitations
 # under the License.
 
-name: fory_test
-description: Apache Fory Dart consumer and integration tests
-version: 1.5.0-dev
+name: inheritance_test_models
+description: Provider-owned Dart hierarchy models used by Fory inheritance tests.
+version: 1.0.0
+publish_to: none
 
 resolution: workspace
 
@@ -25,11 +26,9 @@ environment:
   sdk: ^3.7.0
 
 dependencies:
-  external_type_test_models: 1.0.0
-  fory: 1.5.0-dev
+  fory:
+    path: ../fory
 
 dev_dependencies:
-  build_runner: '>=2.7.0 <3.0.0'
+  build_runner: ">=2.7.0 <3.0.0"
   lints: ^6.1.0
-  test: ^1.31.1
-  vm_service: ^15.0.0
diff --git a/dart/pubspec.yaml b/dart/pubspec.yaml
index 42d9e857f..1e3a14799 100644
--- a/dart/pubspec.yaml
+++ b/dart/pubspec.yaml
@@ -26,7 +26,7 @@ environment:
 # Add regular dependencies here.
 dependencies:
   fory: 1.5.0-dev
-  build_runner: '>=2.7.0 <3.0.0'
+  build_runner: ">=2.7.0 <3.0.0"
 dev_dependencies:
   lints: ^6.1.0
   test: ^1.31.1
@@ -34,3 +34,4 @@ workspace:
   - packages/external-type-test-models
   - packages/fory
   - packages/fory-test
+  - packages/inheritance-test-models
diff --git a/docs/guide/dart/code-generation.md b/docs/guide/dart/code-generation.md
index 01624e300..63e5aee95 100644
--- a/docs/guide/dart/code-generation.md
+++ b/docs/guide/dart/code-generation.md
@@ -56,15 +56,101 @@ For a class owned by another library, define an
 [external structural serializer](external-types.md) with
 `@ForyStruct(target: ExternalType)`.
 
+## Inherited Fields
+
+For an ordinary struct, Fory follows the concrete superclass and applied-mixin
+storage chain. All non-static instance storage is flattened into the concrete
+child's one generated schema, globally ordered with fields declared directly
+on the child. Interfaces and abstract accessors do not add storage.
+
+Field annotations belong to the field declaration. In particular,
+`@ForyField(ignore: true)` on the declaring field is the only way to omit
+ordinary storage. Fory reports a generation error instead of silently omitting
+a field that is inaccessible, hidden, unsupported, or cannot be reconstructed.
+
+| Inherited field                         | Requirement                                                               |
+| --------------------------------------- | ------------------------------------------------------------------------- |
+| Public field                            | No parent annotation; generated child code accesses it directly           |
+| Private field in the child's library    | No parent annotation; the generated part accesses it directly             |
+| Private field in another Dart library   | A public boundary in that declaring library uses `exposePrivateFields`    |
+| Field marked `@ForyField(ignore: true)` | Omitted from the schema; no access or construction requirement is checked |
+
+To expose private state from a library that owns a base class:
+
+```dart
+// package:model_owner/base.dart
+import 'package:fory/fory.dart';
+
+part 'base.fory.dart';
+
+@ForyStruct(exposePrivateFields: true)
+abstract class AccountBase {
+  AccountBase(String tenantId) : _tenantId = tenantId;
+
+  final String _tenantId;
+
+  String get tenantId => _tenantId;
+}
+```
+
+The consumer annotates only its concrete type:
+
+```dart
+// lib/account.dart
+import 'package:fory/fory.dart';
+import 'package:model_owner/base.dart';
+
+part 'account.fory.dart';
+
+@ForyStruct()
+final class Account extends AccountBase {
+  Account(String tenantId) : super(tenantId);
+}
+```
+
+`exposePrivateFields` defaults to `false` and authorizes only the generated
+private-field access companion owned by that library. It does not control field
+discovery or same-library private access. Placing it on `Account` cannot expose
+private fields owned by `model_owner`. If a hierarchy contains private fields
+from several libraries, each declaring library needs its own public opted-in
+boundary.
+
+The child source must import the provider namespace without hiding the
+generated companion. A barrel is valid when it re-exports both the public
+boundary and the companion. Across packages, generate and publish the
+provider's `.fory.dart` part before building a consumer.
+
+For example, a restrictive barrel for the model above must include both names:
+
+```dart
+export 'base.dart'
+    show AccountBase, $AccountBaseForyFieldAccess;
+```
+
+Non-ignored `final` and `late final` fields require an identity-preserving path
+from a parameter of the concrete child's selected generative constructor to
+the exact field. Initializing formals, super formals, redirects, and direct
+constructor initializers are supported. A parameter with the same name is not
+proof if the constructor ignores or transforms it. Use
+`@ForyField(ignore: true)` or a manual serializer when the object cannot be
+reconstructed without custom logic.
+
+The concrete child is registered independently and owns the only serializer
+for its flattened schema. Parent serializers are not nested or invoked.
+Inherited reference annotations feed the existing reference behavior exactly
+like fields declared directly on the child.
+
 ## Step 2 — Run the Generator
 
 From the directory that contains your `pubspec.yaml`:
 
 ```bash
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
 
-This emits a `.fory.dart` file next to your source file. Re-run this command any time you add or rename annotated types.
+This emits a `.fory.dart` file next to your source file. Re-run this command any
+time you add or rename annotated types, change hierarchy storage, or change an
+exposure boundary.
 
 ## Step 3 — Register and Use
 
@@ -107,6 +193,9 @@ class Event {
 
 When using evolving structs, also assign stable field IDs with `@ForyField(id: ...)` before you ship your first payload — those IDs are how Fory matches fields after a schema change.
 
+Inherited and direct fields share this one ID namespace. Do not reuse an ID in
+another superclass, mixin, or child field.
+
 ## Choosing Generated or Manual Serialization
 
 Use an [external structural serializer](external-types.md) when another
diff --git a/docs/guide/dart/external-types.md b/docs/guide/dart/external-types.md
index 38314a33b..827c058e0 100644
--- a/docs/guide/dart/external-types.md
+++ b/docs/guide/dart/external-types.md
@@ -62,14 +62,21 @@ must declare each schema field as `late final` without an initializer. Each
 serialized field name and Dart type, including nullability and generic
 arguments, must exactly match an accessible getter on the target.
 
-Public target fields count toward the graph-memory budget automatically. Use
+The declaration's field list is the complete external schema. Fory does not
+automatically add fields from the target, its superclass, or its mixins. You
+may explicitly declare an accessible inherited target property just like any
+other schema field. `exposePrivateFields` applies only to ordinary Dart
+hierarchies and is invalid with `ForyStruct.target`.
+
+Public target fields count toward the graph-memory budget automatically, but
+that accounting does not add them to the serialized schema. Use
 `@ForyField(ignore: true)` on an extra declaration field to count other storage
 without serializing it.
 
 Run the generator as usual:
 
 ```bash
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
 
 ## Register and Use the Target
diff --git a/docs/guide/dart/grpc-support.md b/docs/guide/dart/grpc-support.md
index 43f2a3a17..58f77a2a1 100644
--- a/docs/guide/dart/grpc-support.md
+++ b/docs/guide/dart/grpc-support.md
@@ -78,7 +78,7 @@ Then run `build_runner` once to emit the Fory serializer part file for the
 generated models (this step is required before the code can run):
 
 ```bash
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
 
 For this schema, the Dart generator emits (the model file and module are named
@@ -281,7 +281,7 @@ grpc-dart APIs, but `fory` intentionally does not depend on gRPC.
 
 ### Generated Code References a Missing `.fory.dart` Part
 
-Run `dart run build_runner build --delete-conflicting-outputs` after generating
+Run `dart run build_runner build` after generating
 or regenerating the Dart sources. The serializer part file is produced by
 `build_runner`, not by `foryc`.
 
diff --git a/docs/guide/dart/index.md b/docs/guide/dart/index.md
index 8c201f6ab..8097bc48e 100644
--- a/docs/guide/dart/index.md
+++ b/docs/guide/dart/index.md
@@ -29,6 +29,7 @@ languages.
 - **Xlang**: serialize in Dart, deserialize in Java, Go, C#, and more without writing any glue code
 - **Platform support**: use the same generated-serializer API on Dart VM/AOT, Flutter, and web
 - **Fast**: generated serializer code replaces reflection during serialization
+- **Inheritance**: ordinary structs flatten concrete superclass and mixin storage into one schema
 - **Schema evolution**: add or remove fields without breaking existing messages
 - **Circular references**: optional reference tracking handles shared or recursive object graphs
 - **Escape hatch**: write a manual serializer for any type that cannot be annotated
@@ -106,17 +107,24 @@ void main() {
 Generate the companion file before running the program:
 
 ```bash
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
 
 `PersonForyModule` is generated by `build_runner`. The `name` value is how peers in other languages identify the same type; keep it stable once your service is in production. Use `.` inside `name` to add a namespace prefix.
 
+An ordinary annotated class includes its concrete superclass and applied-mixin
+storage. Public and same-library private inherited fields need no parent
+annotation. See [Code Generation](code-generation.md) for cross-library private
+fields and constructor requirements.
+
 ## API Overview
 
 - `Fory(...)` — create a serializer instance; create once and reuse it
 - `fory.serialize(value)` — returns `Uint8List` bytes
 - `fory.deserialize<T>(bytes)` — returns a `T`
 - `@ForyStruct()` — marks a class for code generation
+- `@ForyStruct(exposePrivateFields: true)` — lets another library's generated
+  child serializer access private state owned by this library
 - `@ForyStruct(target: Type)` — generates an external structural serializer
 - `@ForyField(...)` — per-field options and canonical `type:` overrides
 - `@ListField(...)`, `@SetField(...)`, `@MapField(...)` — container sugar for nested `type:` trees
diff --git a/docs/guide/dart/schema-evolution.md b/docs/guide/dart/schema-evolution.md
index 6239dc45c..741bf9cef 100644
--- a/docs/guide/dart/schema-evolution.md
+++ b/docs/guide/dart/schema-evolution.md
@@ -62,6 +62,11 @@ class UserProfile {
 
 If you add field IDs after payloads are already in production, existing stored messages won't have them and evolution won't work correctly.
 
+For an ordinary inherited struct, the concrete child's schema contains every
+non-ignored field from its superclass and applied-mixin storage chain. Assign
+IDs across that complete flattened hierarchy; an ID used by a parent or mixin
+cannot be reused by the child.
+
 For an [external structural serializer](external-types.md), the local
 serializer declaration supplies the evolving schema and each declaration field
 must match the corresponding target property.
@@ -83,6 +88,9 @@ must match the corresponding target property.
   values exactly.
 - Change the registration identity (`id` or `name`) of a type after messages are in production.
 - Change a field's logical meaning without changing its ID.
+- Introduce field hiding that leaves both an ancestor and child storage slot;
+  generation rejects this shape because both slots cannot be addressed
+  faithfully.
 
 ## Xlang Notes
 
@@ -102,6 +110,21 @@ Use `compatible: false` only when every reader and writer always uses the same s
 final fory = Fory(compatible: false);
 ```
 
+## Regenerating Inherited Schemas
+
+Regenerate every affected `.fory.dart` part after adding inherited storage,
+changing a superclass or mixin, or changing `exposePrivateFields`. Generate a
+dependency package's provider part before building its consumers.
+
+Older Dart generated code did not include ordinary inherited fields. After
+regeneration, compatible schemas use their normal missing-field and
+unknown-field behavior. Fixed schemas written without the inherited fields are
+not compatible with the corrected flattened schema; Fory does not add a
+special legacy reader for those payloads.
+
+This migration changes generated field discovery only. It does not change the
+runtime reference protocol.
+
 ## Related Topics
 
 - [Configuration](configuration.md)
diff --git a/docs/guide/dart/schema-metadata.md b/docs/guide/dart/schema-metadata.md
index 9d189ffa8..61f93c522 100644
--- a/docs/guide/dart/schema-metadata.md
+++ b/docs/guide/dart/schema-metadata.md
@@ -21,6 +21,10 @@ license: |
 
 Add `@ForyField(...)` to a field inside a `@ForyStruct()` class to change how that field is serialized.
 
+For ordinary inheritance, metadata stays with the storage declaration and is
+applied when any concrete annotated child discovers that field. A child
+redeclaration does not replace or merge the ancestor field's metadata.
+
 The same annotations apply to fields in an
 [external structural serializer declaration](external-types.md).
 
@@ -38,10 +42,12 @@ The same annotations apply to fields in an
 
 ## `ignore`
 
-Exclude a field from serialization entirely. Useful for cached, computed, or
-UI-only values that should not land in a persisted or transmitted message. The
-field still contributes to graph-memory accounting; because it has no wire
-representation, do not combine `ignore` with other `ForyField` options.
+Exclude the declaring field from serialization entirely. This is the only
+supported way to omit ordinary instance storage, including inherited storage.
+Useful examples include cached, computed, or UI-only values that should not
+land in a persisted or transmitted message. The field still contributes to
+graph-memory accounting; because it has no wire representation, do not combine
+`ignore` with other `ForyField` options.
 
 ```dart
 @ForyField(ignore: true)
@@ -59,6 +65,9 @@ String name = '';
 
 Once a payload is shared across services, never reuse an `id` for a different field.
 
+An ordinary child has one flattened field namespace. IDs must therefore be
+unique across its child, superclass, and applied-mixin declarations.
+
 ## `nullable`
 
 Explicitly marks a field as nullable or non-nullable, overriding what Fory infers from the Dart type. Use this when the Dart type is non-nullable but you want Fory to accept `null` on the wire (e.g., reading messages from an older producer that can omit the field).
@@ -81,6 +90,10 @@ List<Object?> sharedNodes = <Object?>[];
 
 Note: scalar types like `int`, `double`, and `bool` never benefit from reference tracking even if `ref: true` is set.
 
+An inherited `ref` annotation behaves exactly like the same annotation on a
+field declared directly by the concrete child. Inheritance does not introduce
+another reference-tracking mode or owner.
+
 ## `dynamic`
 
 Controls whether Fory writes the concrete type of the field value into the payload.
diff --git a/docs/guide/dart/supported-types.md b/docs/guide/dart/supported-types.md
index 55f3858b8..6af5f9377 100644
--- a/docs/guide/dart/supported-types.md
+++ b/docs/guide/dart/supported-types.md
@@ -130,11 +130,27 @@ class User {
 }
 ```
 
+Ordinary annotated classes may extend classes and apply mixins. Fory includes
+every concrete instance storage field from that hierarchy in one child schema,
+including inherited generic fields after specialization. Public fields and
+private fields in the child's Dart library are accessed directly.
+Cross-library private storage is supported only when its declaring library
+provides a public boundary annotated with
+`@ForyStruct(exposePrivateFields: true)`.
+
+Abstract getters, interfaces, static members, and other declarations without
+instance storage are not fields. A real non-ignored storage field must have a
+supported type, an unambiguous access path, and a valid reconstruction path;
+otherwise generation fails. In particular, each `final` or `late final` field
+must receive the decoded value unchanged through the concrete child's
+constructor chain.
+
 See [Code Generation](code-generation.md).
 
 Classes owned by another library can use an
 [external structural serializer](external-types.md) when their public fields
-and construction match a local Fory schema declaration.
+and construction match a local Fory schema declaration. External declarations
+list their schema fields explicitly and do not scan the target hierarchy.
 
 ## Collections
 
diff --git a/docs/guide/dart/troubleshooting.md b/docs/guide/dart/troubleshooting.md
index fba1ef59b..5e184fe8d 100644
--- a/docs/guide/dart/troubleshooting.md
+++ b/docs/guide/dart/troubleshooting.md
@@ -33,7 +33,7 @@ The writer is sending a native-mode payload. Make sure every peer writes the xla
 
 Fory does not know how to serialize or deserialize this type. Fix it by:
 
-1. Running code generation if you haven't: `dart run build_runner build --delete-conflicting-outputs`
+1. Running code generation if you haven't: `dart run build_runner build`
 2. Calling the generated `register` function (or `registerSerializer`) for the type **before** calling `serialize` or `deserialize`.
 3. Registering **all** types that appear in a message, not just the root type. For example, if `Order` contains an `Address`, register both.
 
@@ -42,11 +42,55 @@ Fory does not know how to serialize or deserialize this type. Fix it by:
 Regenerate code:
 
 ```bash
-cd dart/packages/fory
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
 
-If you moved files or renamed types, rebuild before re-running analysis or tests.
+Run the command from the package that owns the source. If a dependency exposes
+private hierarchy fields, generate that provider package first and ensure its
+published source contains the generated `.fory.dart` part, then regenerate the
+consumer. If you moved files, renamed types, or changed a hierarchy, rebuild
+before re-running analysis or tests.
+
+## Inherited private field is not accessible
+
+Ordinary hierarchy discovery includes private storage even when it is declared
+in another Dart library. Privacy affects generated access, not whether the
+field exists in the schema.
+
+- A private field declared in the same library as the child needs no parent
+  annotation.
+- For a private field declared in another library, annotate a public hierarchy
+  boundary in that declaring library with
+  `@ForyStruct(exposePrivateFields: true)`.
+- Import that provider directly, or use a barrel that re-exports both the
+  public boundary and its generated `$<Boundary>ForyFieldAccess` companion.
+- If several libraries declare private fields in the hierarchy, each library
+  must provide its own boundary.
+
+Putting `exposePrivateFields: true` only on the consumer child cannot authorize
+another library's private state. To omit a field intentionally, place
+`@ForyField(ignore: true)` on its declaration. Fory does not silently omit an
+inaccessible inherited field.
+
+## Final inherited field cannot be reconstructed
+
+A non-ignored `final` or `late final` field must receive its decoded value
+unchanged from a parameter of the concrete child's selected generative
+constructor. Fory can follow initializing formals, super formals, redirects,
+and direct constructor initializers across the hierarchy.
+
+A parameter with the same name and type is insufficient if it is unused or its
+value is cast, transformed, or passed through a function. Forward the decoded
+value directly to the exact field, mark the field declaration with
+`@ForyField(ignore: true)`, or use a manual serializer.
+
+## Inherited field is hidden
+
+A subclass field or accessor can hide an ancestor storage slot while both
+physical slots remain on the object. Fory rejects this shape instead of
+choosing one slot and losing the other. Rename or remove the hiding member,
+ignore the ancestor field at its declaration when omission is intentional, or
+use a manual serializer.
 
 ## External target generation fails
 
@@ -141,7 +185,7 @@ matrix and platform guidance.
 Main Dart package:
 
 ```bash
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 dart analyze
 dart test
 ```
@@ -150,7 +194,7 @@ Integration test package:
 
 ```bash
 cd dart/packages/fory-test
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 dart test
 ```
 
diff --git a/docs/guide/dart/type-registration.md b/docs/guide/dart/type-registration.md
index 7948e7afa..0a2f6bcae 100644
--- a/docs/guide/dart/type-registration.md
+++ b/docs/guide/dart/type-registration.md
@@ -65,6 +65,16 @@ Call the generated `register` function from the `.fory.dart` file. It installs a
 UserModelsForyModule.register(fory, User, id: 100);
 ```
 
+For an ordinary inherited type, register the concrete annotated child. Its
+generated serializer already owns the complete flattened child schema; Fory
+does not require runtime registration of a superclass or mixin merely because
+it contributes fields.
+
+Register an independently annotated concrete parent only when values whose
+runtime type is that parent are also serialized. A provider-only
+`@ForyStruct(exposePrivateFields: true)` boundary supplies generated field
+access and has no registration entry of its own.
+
 External structural serializers use the same generated registration API. Pass
 the external target type:
 
@@ -97,6 +107,8 @@ See [Manual Serializers](custom-serializers.md) for how to implement a serialize
 
 - Register **before** the first `serialize` or `deserialize` call.
 - Register **every** class that can appear in a message, not only the root type.
+- Do not register generated private-field access companions; register only
+  concrete serialized types.
 - Keep IDs (or names) **stable** once payloads are persisted or exchanged across services. Changing them will break deserialization of old messages.
 - Do not mix a numeric ID on one side with a name on the other for the same type.
 
diff --git a/docs/guide/dart/web-platform-support.md b/docs/guide/dart/web-platform-support.md
index f329be4d5..0d0190d27 100644
--- a/docs/guide/dart/web-platform-support.md
+++ b/docs/guide/dart/web-platform-support.md
@@ -36,6 +36,8 @@ Fory Dart supports:
 - Flutter web applications.
 - Generated `@ForyStruct` serializers and manually registered serializers on
   all supported targets.
+- Ordinary generated inheritance, including typed cross-library private-field
+  companions.
 - External structural serializers generated with `@ForyStruct(target: ...)`.
 
 ## Code Generation Is Required
@@ -75,13 +77,20 @@ Generate the companion file before building or testing:
 
 ```bash
 cd dart/packages/fory
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 ```
 
 The registration call is the same on VM/AOT, Flutter, and web. Manual
 serializers use `registerSerializer(...)`; generated structs use the generated
 `register` wrapper.
 
+Inherited fields use the same statically generated code on every platform.
+Public and same-library private fields are accessed directly. A provider
+library annotated with `@ForyStruct(exposePrivateFields: true)` emits typed
+static access methods for its own private state; it does not use mirrors,
+callbacks, runtime member lookup, or a parent serializer. Generate a dependency
+provider before compiling its consumer.
+
 ## 64-Bit Integer Rules
 
 Dart VM `int` values are signed 64-bit values. Dart web `int` values are backed
@@ -182,11 +191,23 @@ web:
 
 ```bash
 cd dart/packages/fory
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 dart test
 dart test -p chrome
 ```
 
+For an application smoke test, also compile and execute the actual generated
+entry point:
+
+```bash
+dart compile js --fatal-warnings bin/app.dart -o build/app.js
+node build/app.js
+```
+
+Use a model with the same hierarchy and imports as the production application
+when validating cross-library private fields. Compilation alone does not prove
+that registration and round-trip execution work.
+
 If Chrome tests fail with a stale generated file or missing part file, rerun
 `build_runner` and then retry the test command from `dart/packages/fory`.
 
diff --git a/docs/guide/dart/xlang-serialization.md b/docs/guide/dart/xlang-serialization.md
index 639878b5d..2bea4ca01 100644
--- a/docs/guide/dart/xlang-serialization.md
+++ b/docs/guide/dart/xlang-serialization.md
@@ -83,7 +83,9 @@ ExternalSerializersForyModule.register(
 ```
 
 The declaration's field IDs, names, nullability, and wire-width annotations
-define the Dart-side xlang schema.
+define the Dart-side xlang schema. An external declaration may explicitly list
+an accessible inherited target property, but Fory does not automatically scan
+the external target hierarchy.
 
 ## Dart to Java Example
 
@@ -189,6 +191,17 @@ Fory matches fields by name or by stable field ID. For robust cross-language int
 5. Use `Timestamp`, `LocalDate`, and `Duration` for temporal fields rather than raw `DateTime`.
 6. Validate real round trips across all languages before shipping.
 
+For an ordinary Dart class, Fory flattens concrete superclass and applied-mixin
+storage into the annotated child's one struct schema. Parent and child fields
+share one field-ID namespace and one canonical ordering, so the peer language
+should define the equivalent flat field set. A parent is not encoded as a
+nested object.
+
+Inherited `@ForyField(ref: true)` and nested container reference metadata use
+the same reference behavior as fields declared directly on the child.
+Inheritance does not change xlang reference framing or add parent-level
+reference state.
+
 ## Type Mapping Notes for Dart
 
 Because Dart `int` is not itself a promise about the exact xlang wire width, prefer explicit field metadata when exact cross-language interpretation matters:
@@ -233,7 +246,7 @@ Before relying on a cross-language contract in production, test a payload end-to
 Run the Dart side:
 
 ```bash
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 dart analyze
 dart test
 ```
diff --git a/docs/specification/xlang_implementation_guide.md b/docs/specification/xlang_implementation_guide.md
index 311f69a65..8778a3741 100644
--- a/docs/specification/xlang_implementation_guide.md
+++ b/docs/specification/xlang_implementation_guide.md
@@ -1368,7 +1368,7 @@ The normal Dart integration path is:
 2. annotate field overrides with `@ForyField`
 3. run `build_runner`
 4. call the generated per-library helper, such as
-   `<InputFile>Fory.register(...)`, to bind private generated metadata and
+   `<InputFile>ForyModule.register(...)`, to bind private generated metadata and
    register generated types
 
 Generated code should emit:
@@ -1382,6 +1382,182 @@ The public helper should be a thin generated wrapper around the Fory
 registration API, not a public global registry or a second unrelated
 registration API family.
 
+### Dart Ordinary Struct Inheritance
+
+Ordinary Dart `ForyStruct` inheritance is a code-generation-time field
+discovery, normalization, access, construction, and flattening change. It does
+not redesign the runtime reference protocol.
+
+For a concrete annotated child, the generator walks the instantiated
+superclass and applied-mixin storage chain rather than only the child's direct
+`element.fields`. Each layer's `InterfaceType.element.fields` exposes its
+declared elements, including private declarations in another library. Dart
+privacy controls which generated expression can access an element; it does not
+control whether the element is discovered.
+
+The storage collector:
+
+1. visits the instantiated superclass;
+2. visits actually applied mixins in application order;
+3. visits the current class;
+4. collects concrete instance storage declared by each layer exactly once.
+
+It excludes `Object`, interfaces, mixin `on` constraints, abstract accessors,
+static fields, external fields, and synthetic members that do not own storage.
+A mixin slot is identified by its application site and declaring field, so
+multiple applications cannot be collapsed by spelling or `baseElement`.
+
+Every discovered storage field follows one pipeline:
+
+```text
+complete hierarchy discovery
+  -> declaration-owned @ForyField(ignore: true)
+  -> concrete generic substitution
+  -> direct or companion access resolution
+  -> constructor validation
+  -> one globally sorted child schema
+```
+
+`@ForyField(ignore: true)` on the declaring field is the only stage that may
+remove real ordinary storage. Ignored fields bypass type, access, construction,
+wire identity, and codec work but remain in the concrete object's shallow
+graph-memory field count. An inaccessible, private, final, hidden, unsupported,
+or otherwise unresolved non-ignored field is a generation error.
+
+Ownership is fixed:
+
+| Concern                          | Owner                                            |
+| -------------------------------- | ------------------------------------------------ |
+| Field identity and annotations   | Declaring storage field                          |
+| Hierarchy discovery/substitution | Concrete-child generator                         |
+| Cross-library private permission | Public boundary in the field's declaring library |
+| Schema, sort, and codec          | Concrete annotated child                         |
+| Construction                     | Selected concrete-child generative constructor   |
+| Reference analysis/publication   | Existing concrete-child serializer path          |
+| Graph-memory self charge         | One concrete child object                        |
+| External target field list       | Explicit external serializer declaration         |
+
+Public inherited fields and private inherited fields declared in the child's
+library use direct generated access and require no parent annotation. A private
+field declared in another library remains discovered, but requires
+`ForyStruct(exposePrivateFields: true)` on a public hierarchy boundary in that
+field's declaring library. The option defaults to `false`, is invalid with
+`ForyStruct.target`, and authorizes only provider-library access generation. It
+does not enable discovery, alter same-library access, change field inclusion,
+or let a consumer authorize another library's private state.
+
+A public boundary may expose same-library private storage inherited from a
+private class or mixin. If private fields come from several Dart libraries,
+each declaring library must independently provide an opted-in public boundary
+and visible companion. The nearest qualifying child-visible boundary in the
+declaring library is selected.
+
+The provider's `.fory.dart` part emits a public `@nodoc` typed static access
+companion. It emits an exact getter for each exposed non-ignored private field,
+a setter only for mutable storage, and no setter for `final` or `late final`
+storage. The receiver is the public boundary type. Generic bounds and every
+type nested in a public signature must be nameable outside the provider
+library. The companion must not use `dynamic`, `Object?` bridge casts,
+reflection, callbacks, runtime lookup, a parent serializer, or stored runtime
+state.
+
+The child source must have a direct import or re-export namespace that exposes
+the public boundary and companion. Provider output must be generated and
+published before a dependent package is built. Missing permission, a hidden or
+ambiguous companion namespace, an unnameable signature, or dispatch that no
+longer reaches the exact storage slot is a generation error. A child must also
+validate the complete concrete hierarchy so field hiding below the boundary
+cannot redirect a generated getter or setter to another slot.
+
+Every non-ignored `final` or `late final` field must be initialized by a
+statically proven identity flow:
+
+```text
+selected concrete-child constructor parameter
+  -> redirect or super parameter
+  -> exact storage field
+```
+
+Accepted edges are exact field formals, super formals, direct parameter
+references in constructor field initializers, and direct parameter references
+in redirecting or super-constructor arguments. Types must remain identical
+after concrete generic substitution, including nullability. Calls, operators,
+casts, null assertions, constants, constructor-body assignment, and matching
+names without element identity are not proof. A declaration initializer on a
+non-ignored final field is unsupported because the decoded value cannot own
+that slot. There is no post-construction final write, reflection, or fallback.
+
+Mutable fields connected to selected constructor parameters are initialized
+once through the same exact identity flow. Remaining mutable fields require an
+exact setter and are restored after construction. Required constructor
+parameters must have one unambiguous field source. Optional named parameters
+may be omitted; an omitted optional positional parameter cannot be followed by
+a passed positional parameter. Constructor arguments and assignments are
+matched by resolved storage-field identity rather than field-name strings.
+
+The concrete child owns one `GeneratedStructSchema`, one canonical field sort,
+one serializer and descriptor cache, one reconstruction, one reference
+publication path, and one graph-memory owner. Parent serializers are neither
+nested nor invoked, and parent runtime registration is not required. A
+separately annotated concrete parent has its own independently flattened
+schema only for values of that exact type.
+
+Direct and inherited fields feed one normalized list into the existing
+recursive reference analysis and `needsRootRef` calculation. Inherited
+`ref: true` and nested container metadata behave like equivalent direct child
+fields. Inheritance adds no reference state, `ReadContext` API, serializer
+signature, call-contract change, runtime branch, slot, sentinel, callback,
+wrapper, compatible-layout state, or parent reference owner. A failure shared
+by the equivalent flat model is a separate reference-subsystem issue.
+
+Java provides only the flattened-model comparison for this feature: its object
+serializer extends one field-descriptor list across inheritance and adds no
+inheritance-specific reference state. Java's existing reference contract uses
+the `readRefIds` stack, real reference IDs, and its `-1` sentinel to associate
+`reference(obj)` with the current serializer invocation. That contract differs
+from Dart's current reference subsystem and is not a design to transplant as
+part of Dart hierarchy support. Dart inheritance is required only to match its
+own equivalent flat model.
+
+Hierarchy traversal, substitution, access validation, and constructor proofs
+run during generation. Existing flat serializers gain no runtime work, public
+and same-library inherited fields emit the same direct operations as equivalent
+flat fields, and cross-library private fields add only an inlineable typed
+static companion call. Generation must not introduce runtime hierarchy
+traversal, allocation, callbacks, reflection, or parent dispatch.
+
+The child's shallow graph-memory formula is:
+
+```text
+24 + 4 * actualConcreteStorageFieldCount
+```
+
+It counts every real inherited and ignored storage slot once and adds no parent
+self charge.
+
+Generated diagnostics must identify the concrete child, declaring field,
+declaring library, and failed access or constructor path, then give an
+actionable remedy. Typical remedies are adding
+`exposePrivateFields: true` to a public owner-library boundary, importing its
+generated companion, forwarding a constructor value unchanged, placing
+`@ForyField(ignore: true)` on the field declaration, or using a manual
+serializer. Diagnostics must state that hierarchy discovery is independent of
+cross-library access.
+
+This correction changes generated schemas for ordinary children that inherit
+storage. All affected `.fory.dart` files must be regenerated. Compatible mode
+uses normal missing/unknown-field handling; fixed schemas written while
+inherited fields were absent have no legacy reader. Implementations must not
+retain an alternative path limited to child declarations, constructor binding
+based only on names, a late-final setter path, compatibility shim, fallback
+access, or parent serializer delegation.
+
+Acceptance requires that every hierarchy storage slot is either explicitly
+ignored or represented exactly once with a valid type, access path, wire
+identity, and reconstruction path. Equivalent flat and inherited models must
+produce the same canonical schema, wire bytes, reference IDs, and round-trip
+behavior. External target declarations remain explicit and unaffected.
+
 ### Dart External Structural Serializers
 
 Dart external-type serialization extends `ForyStruct` with an optional
@@ -1404,6 +1580,11 @@ reference-tracked paths back to the target are rejected during generation.
 The recursive check includes target elements, keys, and values nested in the
 supported list, set, and map field metadata.
 
+The external declaration's fields are the complete schema. It may explicitly
+name an accessible property inherited by the target, but the generator does
+not automatically scan the external target hierarchy for schema fields.
+`exposePrivateFields` is invalid on an external declaration.
+
 An external object's shallow graph-memory formula is the union of declaration
 fields and public instance fields discovered on the target, its superclasses,
 and applied mixins. A public target field represented by a declaration is
@@ -1472,7 +1653,7 @@ For Dart implementation changes, run at minimum:
 
 ```bash
 cd dart
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 dart analyze
 dart test
 ```
@@ -1481,6 +1662,6 @@ For generated consumer coverage, also run:
 
 ```bash
 cd dart/packages/fory-test
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 dart test
 ```
diff --git a/integration_tests/idl_tests/run_dart_tests.sh b/integration_tests/idl_tests/run_dart_tests.sh
index a35eb01c0..522b88f94 100755
--- a/integration_tests/idl_tests/run_dart_tests.sh
+++ b/integration_tests/idl_tests/run_dart_tests.sh
@@ -7,7 +7,7 @@ python "${SCRIPT_DIR}/generate_idl.py" --lang dart
 
 cd "${SCRIPT_DIR}/dart"
 dart pub get
-dart run build_runner build --delete-conflicting-outputs
+dart run build_runner build
 dart test
 
 IDL_PEER_LANG=dart "${SCRIPT_DIR}/run_java_tests.sh"
diff --git a/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java b/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java
index 85f058b03..f484a46da 100644
--- a/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java
+++ b/java/fory-core/src/test/java/org/apache/fory/xlang/DartXlangTest.java
@@ -31,6 +31,7 @@ import lombok.Data;
 import org.apache.fory.Fory;
 import org.apache.fory.annotation.ForyField;
 import org.apache.fory.annotation.ForyStruct;
+import org.apache.fory.annotation.Int32Type;
 import org.apache.fory.memory.MemoryBuffer;
 import org.apache.fory.memory.MemoryUtils;
 import org.apache.fory.test.TestUtils;
@@ -45,19 +46,12 @@ public class DartXlangTest extends XlangTestBase {
   private static final int DART_SETUP_TIMEOUT_SECONDS = 300;
   private static final File DART_WORK_DIR = new File("../../dart");
   private static final File DART_FORY_TEST_WORK_DIR = new File("../../dart/packages/fory-test");
-  private static final File DART_XLANG_SOURCE_FILE =
-      new File(DART_FORY_TEST_WORK_DIR, "lib/entity/xlang_test_models.dart");
-  private static final File DART_XLANG_GENERATED_FILE =
-      new File(DART_FORY_TEST_WORK_DIR, "lib/entity/xlang_test_models.fory.dart");
-  private static final File DART_EXTERNAL_SOURCE_FILE =
-      new File(DART_FORY_TEST_WORK_DIR, "lib/model/external_serializers.dart");
-  private static final File DART_EXTERNAL_GENERATED_FILE =
-      new File(DART_FORY_TEST_WORK_DIR, "lib/model/external_serializers.fory.dart");
+  private static final File DART_INHERITANCE_PROVIDER_WORK_DIR =
+      new File("../../dart/packages/inheritance-test-models");
   private static final String DART_MODULE =
       "packages/fory-test/test/cross_lang_test/xlang_test_main.dart";
   private static final List<String> DART_CODEGEN_COMMAND =
-      Arrays.asList(
-          DART_EXECUTABLE, "run", "build_runner", "build", "--delete-conflicting-outputs");
+      Arrays.asList(DART_EXECUTABLE, "run", "build_runner", "build");
 
   private static final List<String> DART_BASE_COMMAND =
       Arrays.asList(DART_EXECUTABLE, "run", DART_MODULE, "<DART_TESTCASE>");
@@ -99,12 +93,14 @@ public class DartXlangTest extends XlangTestBase {
   }
 
   private static synchronized void ensureGeneratedXlangSpecs() {
-    if (DART_XLANG_GENERATED_FILE.isFile()
-        && DART_XLANG_GENERATED_FILE.lastModified() >= DART_XLANG_SOURCE_FILE.lastModified()
-        && DART_EXTERNAL_GENERATED_FILE.isFile()
-        && DART_EXTERNAL_GENERATED_FILE.lastModified()
-            >= DART_EXTERNAL_SOURCE_FILE.lastModified()) {
-      return;
+    if (!TestUtils.executeCommand(
+        DART_CODEGEN_COMMAND,
+        DART_SETUP_TIMEOUT_SECONDS,
+        Collections.emptyMap(),
+        DART_INHERITANCE_PROVIDER_WORK_DIR)) {
+      throw new IllegalStateException(
+          "Failed to generate Dart inheritance providers by command: "
+              + String.join(" ", DART_CODEGEN_COMMAND));
     }
     if (!TestUtils.executeCommand(
         DART_CODEGEN_COMMAND,
@@ -131,6 +127,30 @@ public class DartXlangTest extends XlangTestBase {
     int age;
   }
 
+  @Data
+  @ForyStruct
+  static class FlatInheritedStruct {
+    @ForyField(id = 1)
+    @Int32Type
+    int childInt;
+
+    @ForyField(id = 2)
+    @Int32Type
+    int parentInt;
+
+    @ForyField(id = 5)
+    boolean childFlag;
+
+    @ForyField(id = 6)
+    boolean parentFlag;
+
+    @ForyField(id = 9)
+    String childText;
+
+    @ForyField(id = 10)
+    String parentText;
+  }
+
   private void runExternalUser(boolean enableCodegen, boolean named) throws IOException {
     Fory fory =
         Fory.builder().withXlang(true).withCompatible(true).withCodegen(enableCodegen).build();
@@ -163,6 +183,43 @@ public class DartXlangTest extends XlangTestBase {
     runExternalUser(enableCodegen, true);
   }
 
+  @Test(groups = "xlang", dataProvider = "enableCodegenParallel")
+  public void testInheritedFlatStruct(boolean enableCodegen) throws IOException {
+    Fory fory =
+        Fory.builder()
+            .withXlang(true)
+            .withCompatible(false)
+            .withCodegen(enableCodegen)
+            .withMetaCompressor(new NoOpMetaCompressor())
+            .build();
+    fory.register(FlatInheritedStruct.class, 1101);
+
+    FlatInheritedStruct expected = new FlatInheritedStruct();
+    expected.childInt = 101;
+    expected.parentInt = 202;
+    expected.childFlag = true;
+    expected.parentFlag = false;
+    expected.childText = "child";
+    expected.parentText = "parent";
+    byte[] javaBytes = fory.serialize(expected);
+
+    ExecutionContext javaContext = prepareExecution("test_inherited_flat_from_java", javaBytes);
+    runPeer(javaContext);
+    byte[] dartRoundTripBytes = readBytes(javaContext.dataFile());
+    Assert.assertTrue(
+        Arrays.equals(dartRoundTripBytes, javaBytes),
+        "Dart inherited round-trip must preserve Java flat bytes.");
+    Assert.assertEquals(fory.deserialize(dartRoundTripBytes), expected);
+
+    ExecutionContext dartContext = prepareExecution("test_inherited_flat_from_dart", new byte[0]);
+    runPeer(dartContext);
+    byte[] dartBytes = readBytes(dartContext.dataFile());
+    Assert.assertTrue(
+        Arrays.equals(dartBytes, javaBytes),
+        "Dart inherited and Java flat writers must emit identical bytes.");
+    Assert.assertEquals(fory.deserialize(dartBytes), expected);
+  }
+
   @Test(groups = "xlang")
   public void testBuffer() throws java.io.IOException {
     super.testBuffer();


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@fory.apache.org
For additional commands, e-mail: commits-help@fory.apache.org