# Structive Design Philosophy and Principles [中文](DESIGN.zh-CN.md) ## 1. Purpose Structive exists to give ordinary C++ structs explicit structural meaning while preserving the native C++ object model. It is not a replacement language, reflection runtime, security boundary, ORM or object framework. It is a structural metadata and managed-property layer that remains close to normal C++. The design target is: ```text ordinary C++ data + explicit schema + intrinsic property capability + metadata + optional managed synchronization + optional runtime adaptation ``` ## 2. Primary principle: enhance, do not replace The first rule is: > **Enhance the struct; do not replace the struct.** A registered field remains a real member. Unregistered state remains normal C++. Raw access remains possible when the C++ type itself permits it. Structive must not require every field to become a wrapper such as `Property`, nor should it force application objects into a second object model. ### Rule If a Structive feature can be implemented as metadata or a thin managed layer without changing the native member model, prefer that design. ## 3. Type information and instance behavior are separate Structive has two architectural layers. ### 3.1 Type layer `Type_Descriptor` and `Object_Schema` contain structural facts: - registered properties; - keys; - intrinsic readable/writable capability; - Attributes; - Constraints; - default synchronization description. This information belongs to the type. ### 3.2 Instance layer `Property_Object` provides instance behavior: - managed read/write; - lock storage for writable synchronization domains; - multi-property guards; - traversal; - type-erased runtime access; - optional per-instance synchronization override. ### Rule Do not move type-level facts into every object instance unless the fact genuinely varies per instance. ## 4. Registration is explicit Structive does not assume every member is a property. ```cpp struct Device : Property_Object { int temperature; int internal_cache; }; ``` If only `temperature` is registered, `internal_cache` does not exist in the Structive schema. ### Rule Registration defines participation. Absence from the schema means absence from Structive. ## 5. Intrinsic capability belongs to the property itself Structive has no built-in access-control subsystem. A property only describes what it intrinsically supports: ```text none read write read_write ``` The capability is normally derived from the accessor. Metadata may explicitly narrow it. ```cpp field<&Device::serial_number>(key<"serial_number">, read_only) ``` This says: > Within the Structive managed model, this property is readable and not writable. It does not say which user, service, GUI or process is allowed to see it. ### Rule Property capability describes structure, not authorization. ## 6. Core must not own external access policy Structive deliberately does not define: ```text internal external persistence role context permission ``` as managed access modes. A GUI can decide which properties are editable. An RPC service can decide which fields are exposed. A persistence layer can decide which fields it saves. Those decisions belong to those systems. Core exposes structural facts; consumers define policy. ### Rule Do not add access policy to Core merely because an adapter needs a policy. The adapter owns that policy. ## 7. Raw access and managed access are distinct contracts These are intentionally different: ```cpp device.temperature = 30; device.write<&Device::temperature>(30); ``` Raw access follows normal C++ rules. Managed access follows the Structive schema and synchronization model. If code writes a `read_only` public member directly, it has intentionally bypassed the Structive contract. ### Rule Structive protects cooperative managed code. It does not pretend to prevent deliberate raw C++ access. ## 8. Read-only metadata must produce a real optimization A stored property that cannot be written through Structive cannot race with another Structive managed writer, because no managed writer exists. Therefore a stored intrinsic read-only property: - receives no lock slot; - contributes no mutex; - ignores broad synchronization defaults; - performs managed reads without lock lookup; - performs managed reads without `shared_lock` construction. This is a structural optimization derived from schema information. ### Rule If the schema proves that synchronization state is unnecessary, do not allocate or execute it. ## 9. Compile-time knowledge should remove runtime work Typed APIs know the selected property at compile time: ```cpp device.read<&Device::serial_number>(); ``` For a stored read-only property, the compiler-visible implementation path bypasses synchronization entirely. Similarly, typed writes to a read-only property are removed by constraints rather than accepted and rejected at runtime. Dynamic key APIs use runtime checks because the key is not known until runtime. ### Rule Static facts should become `constexpr`, `requires` or `if constexpr`, not runtime branches. ## 10. Synchronization describes mutable consistency domains Synchronization exists to coordinate mutable managed state. The default rules are: ```text independent shared unsynchronized ``` Groups allow several mutable properties to share one lock domain. Read-only stored properties are removed from the final resolved lock topology even if a broad rule names them. ### Rule Synchronization topology is about mutable consistency, not property visibility. ## 11. Computed read-only properties are a special case A computed property may itself be read-only while depending on writable fields. The computed property does not contain writable storage. However, its read may require a stable snapshot of mutable dependencies. Dependencies must be explicit Schema facts. The `Synchronized_Computed_Accessor` read view can access only declared direct dependencies, and the computed property's read slot is derived from the dependency graph instead of requiring its key to be repeated in a synchronization group. Writable dependencies that require one atomic snapshot must share one synchronization domain with each other. Read-only stored dependencies are safe to read directly through the synchronized view because they have no managed writer. ### Rule Do not give a read-only stored field a mutex. A computed read uses synchronization only for mutable dependencies that require consistency. ## 12. One Attribute protocol Core and extensions share one Attribute mechanism. An Attribute owns a category and may define whether it is single-valued and inheritable. Core must not create parallel metadata systems for UI, serialization, diagnostics or domain-specific features. ### Rule New metadata domains should extend the Attribute protocol instead of adding a second metadata framework. ## 13. Category ownership must be clear The component that defines an Attribute category owns its semantics. Presentation owns presentation categories. Core can store them, traverse them and expose them generically, but it must not interpret them. This keeps dependency direction one-way: ```text Extension → Core Core -X→ Extension ``` ### Rule Core stores unknown extension metadata without learning extension semantics. ## 14. Defaults are metadata inheritance only `defaults(...)` is for inheritable Attributes. It should not mutate objects or hide procedural behavior. Property-level metadata can override an inheritable default for the same single-valued category. Intrinsic capability is not inheritable because the capability describes each property itself. ### Rule Defaults may reduce metadata repetition, but they must not become an invisible behavior engine. ## 15. Validation is explicit Constraints describe valid values. They do not automatically execute inside every write. ```cpp auto error = validate_property_value<&Device::temperature>(schema, candidate); ``` Field validation, cross-field invariants, transaction boundaries and rollback are separate operations. ### Rule Do not turn `write()` into an implicit workflow containing validation, events, transactions and rollback. ## 16. Runtime access is adaptation, not the primary programming model `Property_Object_Base` provides key-based type-erased access for dynamic systems. Runtime access only uses intrinsic capability. There is no runtime permission mode. An adapter decides whether it should expose a property and whether it should invoke runtime read or write. ### Rule Use member-pointer typed access inside normal C++ business code. Use runtime access at dynamic boundaries. ## 17. Runtime and compile-time errors have different jobs Compile-time typed operations should reject impossible structural operations through constraints. Examples: - writing an intrinsic read-only property; - requesting a typed unique guard for a read-only property; - reading a write-only property. Runtime key APIs report dynamic failures through `Runtime_Access_Result`. The runtime write boundary is copy-input by design: intrinsic `writable` remains a structural fact, while `runtime_copy_writable` states whether the accessor can participate in that type-erased copy boundary. Move-only typed writes therefore do not get mislabeled as structurally non-writable. ### Rule Do not defer a statically knowable property error to runtime. ## 18. Synchronization policy remains replaceable `Shared_Mutex_Policy` provides real shared/exclusive locking. `No_Lock_Policy` removes real mutex storage. NoLock should not allocate fake mutex arrays or construct meaningless lock objects. ### Rule A policy that removes a capability should remove its storage and hot-path cost where possible. ## 19. Per-instance synchronization overrides preserve object-local policy The default resolved topology is shared per type. An object may explicitly receive a `Property_Synchronization` override. Only such an object stores an override layout. Copy/move construction preserves that layout; assignment preserves the target object's synchronization policy. ### Rule Do not make the default path pay the storage cost of a feature that only some instances use. ## 20. Extensions decide domain behavior A persistence extension may choose fields based on its own metadata. A GUI may decide editability. An RPC layer may implement authorization. Those systems may inspect Structive metadata such as `readable`, `writable`, `sensitive` or custom extension Attributes, but Structive does not decide their policy for them. ### Rule Core describes structure. Consumers decide behavior at their boundary. ## 21. Non-goals Property Core is not intended to become: - an authorization engine; - an ORM; - a JSON library; - an RPC framework; - a GUI binding framework; - a transaction manager; - an event bus; - a scripting engine. Such systems can consume Structive but should remain separate. ## 22. Evolution rules When extending Structive, review the change against these rules: 1. Does it enhance normal C++ rather than replace it? 2. Is the information type-level or instance-level? 3. Is this intrinsic structural capability or external policy? 4. Can compile-time information remove runtime work? 5. Does a read-only stored property remain outside the lock topology? 6. Is synchronization limited to mutable consistency? 7. Is validation still explicit? 8. Does the extension own its own category semantics? 9. Is runtime adaptation kept separate from typed business APIs? 10. Is a new Core concept genuinely universal? ## 23. Architectural summary The intended architecture is: ```text Native C++ struct │ ├── raw C++ access │ └── Structive schema │ ├── intrinsic readable/writable capability ├── Attributes ├── Constraints ├── synchronization description │ └── only mutable consistency domains create locks └── managed object ├── typed read/write ├── guards ├── traversal └── intrinsic runtime access External systems ├── GUI policy ├── RPC policy ├── persistence policy └── other domain policy External policies consume Structive facts; they are not Structive Core access modes. ``` The compact statement of the design is: > **Structive describes what a property is and what it intrinsically supports. It does not decide who may use it.**