# Adminive 设计理念与架构原则 本文描述 Adminive 当前架构的设计目标、职责边界和长期演进原则。它不是 API 速查表;具体接口和完整示例仍以项目根目录 `README.md` 为准。 ## 1. 项目定位 Adminive 的目标不是重新发明对象模型、反射系统或属性系统,而是把后端已经存在的 C++ 结构投影成后台管理能力。 可以把职责分成结构层、字段呈现层、视图组合层和最终渲染层: ```text Structive / Object Schema 描述并管理“这个 C++ 对象本身是什么” │ ▼ Field Presentation 描述“单个字段应该以什么语义控件呈现” │ ▼ View Schema / Composition 描述“字段和业务组件如何组成表单、表格和页面” │ ▼ AMIS Adapter / Frontend 把语义结构翻译成具体 UI,并负责纯视觉与响应式细节 ``` 因此 Adminive 更接近一个 backend-driven administration projection layer,而不是一个新的 Property Core。 它围绕同一份后端描述连接这些领域: ```text C++ business type │ ▼ Adminive descriptor │ ├── JSON encode / decode ├── field presentation ├── form / table view schema ├── page composition ├── AMIS adapter ├── HTTP resource service ├── validation / error path ├── managed update └── external transaction ``` 前端消费后端生成的描述结果,而不是重新维护一份业务字段定义。 ## 2. 最核心的设计原则 Adminive 的核心原则可以浓缩为以下几条: 1. **Structive 管结构事实,Adminive 管后台管理语义。** 2. **属性信息必须能够影响真实行为,而不只是生成 UI 文本。** 3. **默认只读,显式声明可写。** 4. **同步只保护 mutable consistency,不承担权限、事务或持久化职责。** 5. **JSON、HTTP、枚举反射、结构体反射都是 Adapter,不进入核心协议。** 6. **外部系统是否暴露字段由外部系统自己决定,Property Core 不做访问控制。** 7. **静态可知的事实尽量在编译期表达,动态输入才留到运行期处理。** 8. **前端是后端描述的消费者,不成为第二个业务规则中心。** 9. **外部副作用事务与内存对象同步保持正交。** 10. **不为了统一形式制造重复抽象;已经由 Structive 解决的问题不在 Adminive 再实现一次。** 后续新增功能如果违反其中任意一条,都应该先重新确认它是否真的属于 Adminive Core。 ## 3. Structive 与 Adminive 的边界 ### 3.1 Structive 负责什么 `third_party/Structive` 负责对象自身的结构能力: ```text Property intrinsic capability Schema read / write read-only optimization synchronization topology lock slot mutex storage guard runtime property access constraint / generic attribute infrastructure ``` 这些能力与 HTTP、JSON、AMIS、数据库、GUI 没有关系。 例如一个 Property 被声明为 intrinsic read-only,Structive 可以直接利用这个事实: ```text read_only ↓ managed write 不存在 ↓ 不进入 writable synchronization topology ↓ 不贡献 lock slot ↓ 不创建对应 mutex ↓ managed read 走零锁路径 ``` 这类优化属于结构本身,因此应由 Structive 完成。 ### 3.2 Adminive 负责什么 Adminive 负责后台管理领域信息和适配: ```text 字段名称与 Field Presentation editable / creatable sensitive required control / visible_on Form View / Table View vertical / horizontal / flow / grid / group / card / tabs 表格列、排序、搜索、筛选、固定列与重排语义 Composition View / Slot JSON 描述协议 AMIS adapter HTTP resource semantics frontend patch Object_Adapter Resource_Transaction ``` 这些信息表达的是“后台系统怎样消费一个结构”,而不是改变 C++ Property 的基本定义。 ### 3.3 不允许再次出现第二套 Property Core Adminive 不应该重新拥有: ```text 另一套 Property Descriptor 另一套 per-field mutex tree 另一套 lock slot 另一套 managed access mode 另一套 runtime type erasure core ``` 如果某个需求本质上属于 Property/Schema/synchronization,应优先落到 Structive;如果它属于后台管理领域,则留在 Adminive。 这种分层避免两个库同时维护相似概念并逐渐产生不同语义。 ## 4. “结构事实”与“后台元数据”必须分开 Adminive 字段同时包含两类信息,但这两类信息不能混为一谈。 第一类是结构事实: ```text 这个字段在 managed model 中是否 writable 这个字段是否参与 synchronization 字段值的真实 C++ 类型 字段 accessor ``` 第二类是后台领域信息,但它本身还必须继续分层: ```text Field Presentation label / description / control / options / visible_on View Schema 字段选择 / 顺序 / group / card / grid / flow / tabs table columns / sort / search / filter / fixed / reorder Composition View Form / Table / Status 等完整组件之间的页面组合 ``` 字段自身的呈现信息不能携带“它在某个表格里排第几列”这种视图语义;同一个 Property 可以同时出现在 Edit View、Detail View 和多个 Table View 中,而不需要重新定义字段本身。 例如: ```cpp ADMINIVE_FIELD(Config, backend_value).read_write() ``` 表示 `backend_value` 在 Adminive managed model 中可以被修改,但并没有因此自动成为前端可编辑字段。 而: ```cpp ADMINIVE_FIELD(Config, frontend_value).editable() ``` 既使它成为 managed writable,也附加“前端 update 可以编辑”的领域语义。 因此: ```text managed writable ≠ frontend editable ``` 这条边界必须长期保持。 ## 5. 后端 UI 的分层模型 Adminive 的目标是让后端修改 C++ 结构和后台描述后,前端无需同步维护业务字段清单即可得到新的界面。因此后端必须拥有页面的**语义结构**,但不应该把所有语义都塞进 Field Descriptor,也不应该直接把 AMIS JSON 当成业务描述语言。 ### 5.1 Object Schema:字段是什么 Object Schema 由 Structive 与 Adminive Descriptor 共同建立,负责字段名、真实 C++ 类型、intrinsic writable、constraint、managed synchronization 等结构事实。这一层不知道表格、Card、Tabs 或 AMIS。 ### 5.2 Field Presentation:一个字段怎么呈现 `Field_Presentation` 只描述单字段呈现: ```text label description control visible_on enum labels/options ``` `control` 使用 `text / multiline_text / number / boolean / select / date / color` 等语义类型,而不是把 `input-text`、`input-date` 之类 AMIS 组件名写进结构描述。AMIS Adapter 再负责语义控件到 AMIS 组件的映射。 ### 5.3 View Schema:这些字段怎么组成一个业务视图 Form View 可以组合: ```text all_fields field frontend vertical horizontal flow grid list group card tabs / tab ``` 后端因此仍然可以完整决定“这十几个字段如何拼接”,但组合关系不再污染字段定义。一个类型可以分别拥有 Edit/Create/Detail View。 Collection View 是集合展示的一等视图。`table/list/cards` 是同一个集合契约的三个 mode,共享字段、查询、CRUD、排序与 create/edit layout;list/cards 的 title/subtitle/body/accent 也属于 `adminive.view` 语义,不允许先生成 table AMIS 再回头修改 renderer JSON。`Table_View` 继续作为 Collection View 内部的表格/查询基础契约,并保留原有 API 语义。 Table View 负责: ```text 哪些字段成为列 列顺序与标题 fixed left/right sortable searchable filterable default sort row reorder create/edit form layout ``` 这里的查询能力同时约束后端 Collection Service。前端不能因为自己画了一个搜索框,就让一个未声明为 searchable/filterable 的字段获得后端查询能力。 ### 5.4 Composition View:完整业务组件怎么组成页面 当页面需要组合已经成型的 Form、Table、Status 或其他业务组件时,使用独立 `Composition_View`。它提供: ```text slot heading frontend vertical horizontal flow grid list group card tabs / tab ``` `slot` 只是引用一个已经生成的业务组件。例如“上方配置表单 + 下方设备表格”可以由后端描述为一个 vertical composition,而不需要业务代码直接拼 AMIS `grid/card/tpl` JSON。`list` 表示显式的纵向条目集合;`frontend` 则只声明子节点及其顺序,不声明最终布局,让前端根据设备、容器或产品形态决定横排、竖排、列表、卡片或表格等视觉组织。 当 `frontend` 交给独立前端消费时,只给 Composition View 仍不够,因为 slot 名本身不说明组件从哪里取得。因此正式外部协议使用 `adminive.composition-manifest`:Manifest 同时包含 Composition View 和 Slot Contract registry。每个 contract 可以声明 `component_kind`、`descriptor_api`、`view_api`、`data_api`、`amis_api` 以及当前 renderer schema。后端在 Manifest 边界验证 Composition 中使用的 slot 都有唯一 contract,前端只按 View 顺序和 Contract 渲染,不维护第二份 slot/业务字段表。 ### 5.5 后端与前端的最终边界 后端负责: ```text 字段存在与否 字段 intrinsic capability 单字段 presentation 字段属于哪个视图 字段顺序与逻辑分组 form/collection/page 的语义组合 collection 排序、搜索、筛选等后端能力 slot contract 与组件来源 ``` 前端负责: ```text 具体 renderer CSS / theme 响应式断点 frontend 节点的最终组合方式 最终列宽和像素级间距 设备尺寸下的视觉降级 ``` 因此核心原则是: > **后端默认拥有页面的语义结构;当后端显式使用 frontend 节点时,只交付内容与顺序,由前端拥有该节点的最终定位;所有节点的视觉实现始终属于前端。** 这两个模式不能混淆:`vertical/horizontal/flow/grid/list/group/card/tabs` 表示后端决定组合语义,`frontend` 表示后端明确放弃该层的布局决定权。前端不能擅自把普通后端组合节点解释成另一种业务结构。 这样既满足“删字段、加字段、改配置只改后端”,又避免后端被某个具体前端框架的 CSS/Grid 实现锁死。 ### 5.6 AMIS 只是 Adapter 依赖方向必须保持: ```text Object Schema ↓ Field Presentation ↓ View Schema / Composition ↓ AMIS Adapter ↓ AMIS JSON ``` 不能反过来让 `Field_Descriptor` 直接保存任意 AMIS JSON;否则 Adminive 会从 backend-driven schema 退化成“在 C++ 里写前端 JSON”。AMIS 表单提交同样只能由 View 与字段权限推导 payload:readonly 字段可以出现在初始展示数据里,但 submit `api.data` 只包含当前 View 选中的 `editable`/`creatable` 字段。这里不能通过放宽 Resource Service 去兼容前端整对象回传,否则会破坏默认只读与 422 拒绝不可写字段的既有安全语义。 ## 6. 默认只读,显式声明可写 Adminive 字段默认是 intrinsic read-only。 这是刻意选择,而不是缺省值碰巧如此。 理由有三点: ### 5.1 安全的默认语义 后台描述中出现一个字段,并不意味着 HTTP/GUI 更新应该天然拥有修改它的能力。 新增字段时默认只读,可以避免因为“忘记配置”而无意进入 managed mutation path。 ### 5.2 让 metadata 真正参与性能优化 只读信息会一路传递到 Structive,同步系统因此可以跳过无意义的锁资源。 这比“所有字段都建 mutex,只是在 write 时检查权限”更符合结构描述系统的价值。 ### 5.3 writable 是结构承诺 一旦调用: ```cpp .editable() .creatable() .read_write() ``` 就意味着 Adminive 正式承认这个字段存在 managed mutation path。 同步、事务和适配器可以据此建立正确行为。 ## 7. 五种字段声明语义 ### 7.1 普通字段 ```cpp ADMINIVE_FIELD(Config, id) ``` 语义: ```text intrinsic read-only 不产生 managed write 不为自身贡献 Structive mutex ``` 它仍然是普通 C++ 成员。直接修改 raw member 属于 raw C++ path,不属于 Adminive/Structive managed contract。 ### 7.2 `.editable()` ```cpp ADMINIVE_FIELD(Config, worker_count).editable() ``` 语义: ```text intrinsic read-write frontend update editable 默认参加 Structive synchronization ``` 这是后台表单最常见的可编辑字段。 ### 7.3 `.creatable()` ```cpp ADMINIVE_FIELD(Device, address).creatable() ``` 语义: ```text intrinsic read-write 允许 create input 默认参加 Structive synchronization ``` 它表达创建流程的领域能力,不等同于普通 update editable。 ### 7.4 `.read_write()` ```cpp ADMINIVE_FIELD(Config, backend_state).read_write() ``` 语义: ```text intrinsic read-write 不因为这一声明自动开放 frontend edit 默认参加 Structive synchronization ``` 适合配置加载、后台代码、服务内部更新等受控路径。 `readable(false)`、`sensitive()`、`required()` 和 `include_default(false)` 是独立的消费/呈现策略,不改变 `.read_write()`、`.editable()`、`.creatable()` 建立的 intrinsic writable 事实。Gallery 的 Field Policy Matrix 直接从 Descriptor 类型生成这些策略,前端不维护第二份字段能力表。 ### 7.5 `.unsynchronized()` ```cpp ADMINIVE_FIELD(Config, atomic_counter).read_write().unsynchronized() ``` 语义: ```text intrinsic writable 不为该路径创建 Structive mutex ``` 只有以下情况适合使用: ```text 字段自身已经是原子类型 更高层已经提供同步 对象生命周期保证单线程 或业务明确接受无同步访问 ``` `.unsynchronized()` 不是性能开关,更不是“我觉得这里应该没事”。它是对同步责任的显式转移。 ## 8. Adminive 不做访问控制系统 Adminive 中的 `editable`、`readable`、`sensitive` 等信息服务于具体后台适配语义,但它们不构成一个通用安全边界。 Structive 更不会定义: ```text internal external persistence RPC GUI admin user ``` 这类访问角色。 原因是不同消费者拥有不同边界: ```text GUI HTTP API RPC plugin script debug tooling persistence ``` 是否向某个消费者暴露字段,应由那个消费者或更高层业务策略决定。 因此 Adminive 的设计原则是: > Property 描述自身能做什么;外部系统决定自己允许做什么。 `sensitive()` 可以指导 Adminive JSON/HTTP 等适配器避免泄漏敏感内容,但不应被误解为 C++ 内存级权限机制。 ## 9. Managed path 与 raw C++ path Adminive 不试图取代普通 C++ 对象。 例如: ```cpp config.worker_count = 8; ``` 如果成员本身允许直接访问,这仍然是合法 C++。 而: ```cpp managed.member<&Config::worker_count>().write([](auto& value) { value = 8; }); ``` 属于 managed path。 managed path 才承诺: ```text 遵守 intrinsic writable 遵守 Structive synchronization 维持回调生命周期边界 与 Resource_Service 的事务流程协作 ``` 直接绕过 managed API 修改 raw member,也同时绕过这些保证。 这符合 Adminive/Structive 的共同原则: > 增强原生 C++ 结构,而不是建立一堵无法绕过的对象墙。 ## 10. 为什么 Managed 回调不能泄漏引用 Managed read/write 的锁只在回调作用域中有效。 如果允许: ```cpp const auto& value = field.read([](const auto& current) -> const auto& { return current; }); ``` 那么引用在返回后仍存在,但锁已经释放,同步契约就失效了。 因此 Adminive 在编译期拒绝这些回调结果: ```text reference pointer reference_wrapper ranges view ``` 需要把结果带出 managed scope 时,应: ```text 返回值副本 或使用 snapshot() ``` 这不是 API 限制,而是锁生命周期本身决定的安全边界。 ## 11. 同步模型:只保护 mutable consistency Structive synchronization 只解决一个问题: > 当前进程内,对可变对象状态进行 managed access 时如何保持一致性。 它不负责: ```text HTTP 权限 数据库事务 配置文件原子替换 业务 validator 外部设备写入 前端确认流程 ``` 默认情况下,可写属性可以拥有独立同步域,以保留并发性。 当一个具体对象实例确实要求跨多个属性的一致快照时,可以使用 shared topology,例如 `Config_Store` 对完整根配置的持久化场景。 这种选择应该由一致性需求驱动,而不是为了“简单”把所有对象默认放到一把全局锁下。 ## 12. 嵌套结构的同步原则 嵌套结构不能简单采用“父对象只要有一个 writable 子字段,所有后代访问都锁父对象”的粗粒度规则。 Adminive/Structive 应尽量利用真实 intrinsic information: ```text 只读叶子 → 不应因为 writable sibling 而产生无意义锁 unsynchronized writable leaf → 不应因为同级字段可写而自动获取无关父锁 可整体替换的 writable ancestor → ancestor 的一致性域覆盖其子树 ``` 最后一条很重要:如果祖先字段本身允许整体赋值,那么对子树任意成员的访问都可能与祖先整体替换发生竞争,此时祖先同步域自然必须参与。 同步边界来自真实 mutation possibility,而不是单纯来自对象树层级。 ## 13. Resource_Service 的职责 `Resource_Service` 是 Adminive 的应用事务编排层,不是新的 Property Core。 一次 managed update 可以包含: ```text 取得一致性快照 应用 frontend patch 校验候选对象 prepare external state 提交 runtime model commit external side effect 失败时恢复 生成最终 response snapshot ``` 当绑定 `Managed_Value` / `Managed_Field` 时,对象一致性由 Structive synchronization 保证。 `Resource_Service` 不应该再创建一棵重复的 per-field mutex 系统。 ## 14. 三种“事务”不能混为一谈 Adminive 中至少存在三类不同一致性问题: ### 14.1 内存对象同步 由 Structive synchronization 负责。 ```text shared/unique lock lock slot synchronization topology ``` ### 14.2 Adminive 外部副作用事务 由 `Resource_Transaction` 负责。 ```text prepare commit rollback ``` 它可以代表配置文件、设备、远端服务等外部动作。 ### 14.3 数据库事务 由数据库自身负责。 数据库 transaction 不能替代进程内对象锁;对象 mutex 也不能替代数据库 ACID。 三者可以在一次业务流程中协作,但职责必须保持独立。 ## 15. Adapter First Adminive Core 不应直接绑定具体第三方库。 当前设计通过 Adapter 连接外部生态: ```text Json_Adapter Value_Adapter Enum_Adapter Reflection_Adapter Object_Adapter Control_Adapter Polymorphic_Adapter HTTP adapter ``` 这条原则有几个直接收益: 1. 核心协议不因为换 JSON 库而变化。 2. 枚举反射和结构体反射可以独立替换。 3. Runtime object 不必为了框架继承某个基类。 4. 第三方依赖停留在桥接层,不污染业务类型。 5. 可以针对单个类型提供精确适配,而不是扩大 Core 特例。 如果未来接入新的 JSON/HTTP/Reflection 实现,应优先增加 Adapter,而不是修改 Adminive 的中心算法。 ## 16. Object_Adapter:运行时对象与配置模型分离 不是所有运行时对象都适合作为可复制配置对象。 例如对象可能包含: ```text atomic mutex socket thread runtime cache file handle ``` `Object_Adapter` 的意义是建立: ```text Runtime Object ⇅ Pure Model ``` Adminive 通过 model 完成: ```text JSON validation patch transaction candidate frontend schema ``` 然后由 `commit()` 将结果应用回真正的 runtime object。 这比要求所有业务类型变成“为了后台框架而设计的 DTO”更符合低侵入原则。 ## 17. Frontend 应保持被动 Adminive 是 backend-driven 系统,因此前端不应该重新维护: ```text 字段名称 业务枚举 显示条件 校验规则 CRUD schema 配置树结构 ``` 前端的职责是: ```text 获取后端描述 按普通组合节点执行后端布局语义 对 frontend 节点自行决定最终定位 渲染页面 提交用户输入 展示后端错误 ``` 如果一个业务字段修改后必须同时修改 C++ descriptor 和 TypeScript hard-coded schema,说明 backend-driven 边界已经被破坏。 前端可以拥有纯 UI 行为,但不应该成为第二份业务 Schema。 ### 17.1 Capability Case Registry 示例画廊不能维护一份和真实实现脱离的 capability 字符串。每个可宣称的能力必须对应一个 Case,Case 至少绑定: ```text id / group / title / summary C++ declaration View or Manifest JSON runtime or renderer JSON preview endpoints tags ``` 总览能力矩阵、活文档和 endpoint 索引从 Case Registry 派生。这样“文档说支持”必须同时能指向协议、运行时入口或自动测试。新增能力如果没有 Case,就不应该自动出现在完整能力清单中。 Collection 能力实验台也遵守这个原则:paging/sort/search/filter/row reorder/status 发送真实 HTTP;column reorder 属于前端表现状态,因此只消费 `column_reorderable` 授权,不伪造后端业务请求。协议边界页直接重复执行非法 Composition、Manifest、readonly、validation、polymorphic 和 Collection query,而不是复制预先写好的错误文字。 ## 18. 编译期与运行期的分工 Adminive 建议遵守以下规则: ```text 编译期已知 → template / concept / requires / descriptor type 运行期才知道 → JSON key / HTTP input / runtime resource / dynamic value ``` 例如 managed writability 是 descriptor type 的静态事实,不应该等一次 HTTP 请求进入以后才通过字符串判断它到底是不是 writable。 而 JSON 字段是否存在、用户输入是否合法,则天然只能在运行时处理。 这可以同时获得: ```text 更早的错误 更清晰的 API 更少的运行时分支 更好的优化机会 ``` ## 19. Metadata 必须具有真实语义 对 Adminive 来说,metadata 不应该只是“最终转成 JSON 的标签集合”。 高质量 metadata 应至少满足一个条件: > 它能被某个明确的系统消费,并改变真实行为。 例如: ```text read_only → 改变 Structive capability 和同步成本 editable → 改变 update schema 和 managed mutability sensitive → 改变序列化/默认值暴露行为 required → 改变输入验证 Field Presentation control → 改变单字段呈现控件 View Schema → 改变字段组合、表格查询能力和页面语义结构 ``` 如果新增 metadata 没有明确消费者,应谨慎加入 Core descriptor,避免逐渐变成无边界的“万能标签袋”。 ## 20. Core 的非目标 Adminive Core 不应该演化成: ```text 通用 ORM 通用 RPC framework 权限系统 数据库 abstraction GUI framework 全功能 serialization library Structive 的替代品 全局 transaction manager ``` 这些领域可以通过 Adapter 或上层模块与 Adminive 协作,但不应因为某个项目需要就全部进入核心协议。 保持非目标清晰,是长期保持 API 简洁的重要手段。 ## 21. 新功能应该放在哪里 新增能力时,可以按下面的判断顺序决定归属。 ### 21.1 它描述 Property 本身吗? 例如: ```text intrinsic readable/writable synchronization topology generic constraint ``` 优先考虑 Structive。 ### 21.2 它描述后台管理领域吗? 例如: ```text editable Field Presentation Form/Table View Composition View AMIS adapter frontend visible condition ``` 属于 Adminive。 ### 21.3 它绑定具体第三方库吗? 例如: ```text nlohmann::json Drogon cpp-httplib magic_enum Boost.PFR ``` 放 Adapter/bridge 层。同一 Core Service 被多个 transport 复用时,transport 只负责把请求完整翻译成 Core 输入;它们不能各自发明更宽松的协议。例如 Collection GET 中除分页/排序保留参数之外的 query 字段必须交给 `Collection_Service` 统一校验,httplib 与 Drogon 都不能静默丢弃 View Schema 未授权的筛选字段。独立状态资源也应保持 `/descriptor`、`/amis`、`/data` 协议对齐,请求上下文与异常只在 transport 边界处理。 ### 21.4 它属于具体业务项目吗? 例如: ```text 某设备必须先断电才能修改配置 某用户角色才能执行操作 某数据库表的迁移规则 ``` 留在业务层,不进入 Adminive Core。 ## 22. API 演进原则 后续修改 Adminive API 时建议遵守: 1. 不破坏“Structive 管结构,Adminive 管后台领域”的依赖方向。 2. 不重新引入重复同步系统。 3. 不把 frontend permission 当成 Property intrinsic capability。 4. 静态事实使用类型系统表达,不退化成运行时 bool 判断。 5. Runtime adapter 保持动态,但不要把动态复杂度传播到普通 typed API。 6. 一个概念只保留一套主要表达方式,避免 `_key`、mode、view 等重复表面接口无边界增长。 7. 不为了少几个函数制造难以理解的模板 DSL。 8. metadata 必须有清晰消费者。 9. 高级能力允许高级 API,但应与普通使用路径分层。 10. 对行为语义的修改必须同时补契约测试和文档。 ## 23. 测试原则 Adminive 的测试不应只验证“JSON 长得对”。 至少应该覆盖五类契约: ### 23.1 Descriptor contract ```text 字段 metadata managed writable synchronized nested descriptor schema validation ``` ### 23.2 View contract ```text Field Presentation 与结构事实分离 Form View 的 frontend/group/card/grid/flow/list/tabs 组合 Table View 的列选择与顺序 Collection View 的 table/list/cards mode、item 语义和独立 CRUD id/reload 目标 Form/Collection submit payload 只包含 View 中可 editable/creatable 的字段 sortable/searchable/filterable/fixed 的后端能力约束 Composition View 的 slot、frontend 与跨组件组合 Composition Manifest 的 Slot Contract 完整性 /view JSON 与 AMIS Adapter 的语义一致性 ``` ### 23.3 Managed synchronization contract ```text read-only zero-lock writable locking unsynchronized path nested lock propagation callback lifetime multi-thread blocking relationship ``` ### 23.4 Adapter contract ```text JSON encode/decode Enum Adapter Reflection Adapter Object Adapter Value Adapter Control Adapter Polymorphic Adapter HTTP Adapter AMIS Adapter ``` ### 23.5 Transaction contract ```text prepare failure runtime commit failure external commit failure rollback snapshot consistency Config Store persistence ``` 这些测试保护的是设计边界,而不仅是当前实现细节。 ### 23.6 Release-safe assertion contract 测试断言不能依赖标准 `assert()`。Release 默认定义 `NDEBUG`,如果测试仍使用 `assert()`,CTest 通过并不能证明表达式被执行。Adminive 测试统一使用始终生效的 `ADMINIVE_CHECK`;`static_assert` 继续用于编译期契约。旧同步 API 的测试文件不保留兼容实现,有价值的并发、nested、unsynchronized 和 callback lifetime 用例迁移到当前 `Managed_Value` 测试。 ### 23.7 Transport 与安装黑盒测试 Service 单元测试不能替代 transport 黑盒测试。Httplib adapter test 必须启动真实 localhost server,再通过 client 请求 descriptor/view/data/amis、Resource Context、Collection CRUD/query/reorder/status 和错误状态。Drogon 保持快速 fake contract test,并提供 `ADMINIVE_ENABLE_REAL_DROGON_TESTS=ON` 的发布 Gate 编译真实 Drogon route/callback/constraint API 和 installed-package consumer。transport 的 `context_factory` 属于最外层边界,`std::exception` 与未知异常都必须在这里转换成结构化 500,不能逃出框架 callback。Collection 的客户端 query/JSON/reorder 输入错误显式映射为 400,字段/对象 validation 为 422;status reader 或其他内部 callback 抛出的未分类异常必须是 500,不能因为共用 `safe_response` 被误报成客户端错误。 安装测试必须先执行 `cmake --install` 到构建目录内的固定 prefix,再从独立 consumer 工程只通过 `find_package(Adminive)` 配置和运行。Adminive 源码树可以使用 pinned bundled nlohmann/json、magic_enum、cpp-httplib 便于可复现 build/test,但安装后的 Adapter target 必须通过外部 CMake package target 解析这些依赖,不能把 bundled header 安装到 `${CMAKE_INSTALL_INCLUDEDIR}` 污染消费者命名空间。install consumer 用独立 package fixture 验证导出 dependency wiring,同时确认公共 install prefix 不包含 bundled dependency header。Boost.PFR 和 Drogon 始终由外部 package 提供。测试 consumer 不允许直接使用 Adminive 源码 include path,否则无法发现 export/install 错误。 ### 23.8 Verification Gate 发布前 Gate 至少包含: ```text Debug full build + CTest Release full build + CTest all public headers standalone include compilation package ZIP structure verification Core + Default/Httplib install consumers ASan + UBSan on Clang/GNU TSan concurrency tests on Clang/GNU ASan on MSVC frontend unit tests frontend production build Playwright Chromium E2E Playwright Edge E2E on Windows real HTTP integration real Drogon compile + installed consumer release gate third-party notice/license/package-content verification ``` CTest label 用于按 `unit/protocol/http/install/package/header/concurrency/frontend/e2e` 分层定位失败。Clang/GNU 的 ASan+UBSan 对 Core Adapter、Advanced Adapter、Safety、Managed、View Schema、Drogon 与真实 Httplib 运行 sanitizer 矩阵,TSan 单独覆盖并发 Managed 路径;MSVC 只运行真实 ASan,配置不支持的 UBSan/TSan 必须直接失败而不能假通过。公共头必须逐个独立 include 编译,安装 consumer 同时验证 Core 与 Default/Httplib,源码 ZIP 必须实际生成并检查精确根文件、旧实现残留和嵌套工程。Sanitizer 是测试配置,不改变 Core API。frontend E2E 验证同一 Manifest 五种布局、Form modes、Collection CRUD/query/reorder/status、422 nested validation、Sensitive 输出过滤、Object Adapter transaction counter、transaction rollback、状态序列、协议错误和缓存策略。Gate 使用确定的目录和端口,不依赖随机构建路径。 ## 24. 设计判断清单 在提交一个新设计前,可以快速检查: ```text [ ] 这是 Structive 已经解决的问题吗? [ ] 这是 Adminive 后台领域真正需要的概念吗? [ ] 它是否把第三方库耦合进 Core? [ ] 它是否把 Field Presentation、View Schema 和 AMIS Adapter 混成一层? [ ] 它是否让 metadata 真正改变行为? [ ] 它是否把访问控制错误塞进 Property Core? [ ] 它是否创建了第二套同步或事务机制? [ ] 静态事实是否能在编译期表达? [ ] 普通用户是否必须理解这个高级机制? [ ] 是否存在更合适的 Adapter 层? [ ] 是否补充了对应契约测试和文档? ``` 如果多数问题无法明确回答,通常意味着抽象边界还没有收敛。 ## 25. 最终架构图 ```text C++ Business Types │ ▼ Structive / Object Schema type / capability / synchronization │ ▼ Adminive Field Descriptor editable / required / sensitive │ ▼ Field Presentation Layer label / control / options / visible │ ┌──────────────┴──────────────┐ ▼ ▼ Form / Detail View Table View group/card/grid/flow/tabs columns/sort/search/filter │ │ └──────────────┬──────────────┘ ▼ Composition View Form/Table/Status slots + page layout │ ┌─────────────────┼─────────────────┐ ▼ ▼ ▼ View JSON Resource Service AMIS Adapter │ ▼ Frontend │ renderer/theme/responsive layout JSON / HTTP / Enum / Reflection / AMIS 都位于消费或 Adapter 层; Structive 和字段结构层不知道具体前端框架。 ``` 最重要的依赖方向始终是: ```text Structive ↑ Adminive Core ↑ Adapters / Service ↑ Application ``` 底层不知道上层领域,上层消费底层能力。这是 Adminive 后续演进时最需要守住的架构边界。