Files
Structive/.agents/skills/structive-development/SKILL.md
T
2026-08-08 22:39:18 +08:00

8.3 KiB

name, description
name description
structive-development 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<T> only for managed APIs:

struct Device : structive::Property_Object<Device> {
    double temperature{};
    int serial_number{};
};
template <>
struct structive::Type_Descriptor<Device> {
    static auto get() {
        using namespace structive;
        return object<Device>(
            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:

auto value = device.read<&Device::temperature>();
device.write<&Device::temperature>(42.0);

Use guards for a consistent multi-property operation:

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:

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:

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.