--- name: structive-development description: Use when defining, extending, modifying, debugging, or reviewing Structive property schemas, Type_Descriptor, Accessors, Attributes, Constraints, managed read/write, guards, synchronization topology, runtime type-erased access, Presentation/extensions, tests, CMake, install/export, or when integrating a C++ type with Structive. --- # Structive Development ## Goal Express structural facts about ordinary C++ objects without replacing the native C++ model. Keep compile-time facts compile-time, keep external policy outside Core, and make managed synchronization pay only for mutable state that actually needs it. ## Read before editing Use these as the source of truth: 1. `AGENTS.md`. 2. `docs/DESIGN.zh-CN.md` or `docs/DESIGN.md` for architecture. 3. `docs/CORE_GUIDE.zh-CN.md` for Core API usage. 4. `docs/EXTENSIONS.zh-CN.md` for extension ownership. 5. The focused header and tests for the feature being changed. When Structive is embedded under Adminive, also respect the parent Adminive instructions, but this nested file owns Structive-specific decisions. ## Define a described object Keep fields as ordinary C++ members and inherit `Property_Object` only for managed APIs: ```cpp struct Device : structive::Property_Object { double temperature{}; int serial_number{}; }; template <> struct structive::Type_Descriptor { static auto get() { using namespace structive; return object( field<&Device::temperature>(key<"temperature">), field<&Device::serial_number>(key<"serial_number">, read_only) ); } }; ``` Do not wrap each member in a new storage type just to make it participate in Structive. ## Choose the correct mechanism | Requirement | Mechanism | |---|---| | Register a stored member | `field<&T::member>(...)` | | Stable runtime/schema name | `key<"name">` | | Narrow intrinsic capability | `read_only` or capability metadata supported by the descriptor | | Unit/sensitive/custom structural metadata | Attribute | | Reusable object-level inheritable metadata | `defaults(...)` with an inheritable Attribute | | Candidate value validity | Constraint + explicit validation helper | | Managed typed read/write | `Property_Object::read` / `write` | | Atomic multi-property consistency | shared synchronization group + typed guard | | Runtime-selected property access | `Property_Object_Base::runtime_read/runtime_write` | | UI/persistence/RPC-specific meaning | Extension-owned Attribute/logic, not Core | ## Managed access rules Prefer typed member/key access in normal C++ code: ```cpp auto value = device.read<&Device::temperature>(); device.write<&Device::temperature>(42.0); ``` Use guards for a consistent multi-property operation: ```cpp auto guard = device.lock_unique<&Device::minimum, &Device::maximum>(); guard.set<&Device::minimum>(20); guard.set<&Device::maximum>(120); ``` Do not replace direct raw C++ access. Raw access intentionally bypasses the managed contract and is the caller's responsibility. ## Synchronization workflow Before changing synchronization code, preserve these invariants: - Only intrinsically writable stored properties contribute mutable lock domains. - Stored read-only properties resolve to `unsynchronized_slot` and managed reads do not acquire a lock. - `independent`, `shared`, `unsynchronized`, groups, compile-time member rules, runtime-key rules, and per-instance overrides all resolve through one lock-slot topology. - Multiple locks are acquired in stable order. - `No_Lock_Policy` removes real mutex storage/locking work instead of simulating mutexes. - Computed read-only properties may require a synchronized dependency view when they derive from mutable state; do not give their immutable storage a fake lock. Synchronization protects in-process consistency only. Do not add transaction or authorization semantics here. ## Runtime adapter workflow Runtime access is a dynamic bridge. It must keep exact type semantics: - Unknown key -> the appropriate not-found result. - Intrinsically unreadable/unwritable -> capability result. - Runtime type mismatch -> type-mismatch result. - Intrinsically writable but not copy-writable -> `unsupported_runtime_write`. - Successful runtime write reuses the managed synchronization path. `runtime_write` copies from a `const` type-erased input. Do not steal/move from that pointer to make move-only types appear runtime-copy-writable. Typed `write` remains the path for move-only values. ## Attribute and Extension workflow Core owns the generic Attribute protocol; the domain that defines a category owns its interpretation. For presentation metadata, use the extension: ```cpp field<&Device::temperature>( key<"temperature">, structive::presentation::label<"Temperature">, structive::presentation::description<"Current temperature">, structive::presentation::group<"Environment">, structive::presentation::order<1> ) ``` Read it through `presentation::describe<...>(schema)`. Presentation fallback from missing label to property key belongs to the extension, not Core. For a new metadata domain, create a category and Attribute in the extension. Do not add an enum/switch to Core merely because an adapter wants a new hint. ## Schema and compile-time contracts Structural mistakes should fail during compilation/configuration whenever they are statically knowable. Existing compile-fail coverage includes duplicate keys, duplicate storage, missing keys, capability/accessor mismatch, incompatible constraints, and foreign synchronization members. When adding another compile-time invariant: 1. Express it in concepts/`requires`/`static_assert` at the schema boundary. 2. Add a source under `core/tests/compile_fail/`. 3. Register it with the existing `structive_expect_compile_failure` mechanism. 4. Do not replace compile-time rejection with a runtime error for typed APIs. Empty schemas are valid. Do not reintroduce pack/CTAD assumptions that require at least one property unless the specific API semantically requires a non-empty set. ## Testing workflow Use focused targets first: - Property/schema/attribute/validation: `structive_property_core_test`. - Runtime type-erased API: `structive_property_runtime_api_test`. - Synchronization/guard/concurrency: `structive_property_synchronization_test`. - Presentation extension: `structive_property_extensions_test`. - Public headers: `structive_header_*` tests. - Installed consumer: `structive_install_consumer_test`. Then run the standalone suite: ```text cmake -S . -B build -DCMAKE_BUILD_TYPE=Debug -DSTRUCTIVE_BUILD_TESTS=ON -DSTRUCTIVE_INSTALL=ON cmake --build build ctest --test-dir build --output-on-failure ``` For synchronization changes, run TSan when the compiler/platform supports it. For Core public API changes, run Debug and Release and then verify the Adminive consumer suite. ## CMake/install rules - Keep `structive::property_core` header-only and `structive::property_extensions` as the extension target unless a deliberate design change says otherwise. - Examples/tests default based on top-level use; embedding Structive as a subdirectory must not force examples into the parent build. - Standalone install/export must continue to support `find_package(Structive CONFIG REQUIRED COMPONENTS Core Extensions)`. - Use paths relative to the Structive CMake file that owns them. ## Review checklist Before handoff, verify: - No second object model or external policy leaked into Core. - Intrinsic capability still matches Accessor capability. - Stored read-only properties still contribute zero lock slots. - Move-only typed write and runtime copy-write semantics remain distinct. - Validation is still explicit. - Extension metadata uses the shared Attribute protocol and Core does not interpret it. - New static invariants have compile-fail coverage. - Public headers compile alone and the standalone install consumer works. - Adminive still builds/tests if the public Structive contract changed. - Only checks actually run are reported as passing. ## Release workflow Structive currently ships without third-party library dependencies. Standalone install/export must stay self-contained apart from the C++ standard library, and `RELEASE.md` must remain part of the installed release metadata. The source tree does not declare an outbound license for Structive project-owned code; do not generate one without an explicit owner decision.