你的第一次受治理变更

你手上已经有一个跑起来的 AuraBoot,里面装着一套 CRM。从这里再花十分钟,你会改掉这套 CRM、亲眼看到平台拒绝写入你没有声明过的东西,然后读到你刚才所有动作的审计记录。

一次坐下来,把整个产品看完。这里没有一步是玩具——真实功能走的就是这条路。

开始之前 —— 先完成 快速开始。你需要一个已经初始化、并且导入了插件的栈,而且能登录进去。

下面所有命令都假设你有一个管理员 token:

BACKEND=http://localhost:3000   # 你在浏览器里打开的那个地址;只有这个端口被映射出来
JWT=$(curl -s -X POST $BACKEND/api/auth/login \
  -H 'Content-Type: application/json' \
  -d '{"email":"[email protected]","password":"Test2026x"}' \
  | python3 -c 'import sys,json;print(json.load(sys.stdin)["data"]["jwt"])')

1. 先看看你已经有了什么

打开 CRM → 线索,从界面上新建一条。能用 —— 而这个页面没有任何人写过。

列表、列、表单、校验,全部来自一份声明:plugins/crm-starter/config/fields/crm_lead.json,加上一份 binding(说明这个模型实际用到哪些字段)。这是第一个理念:模型是 Git 里的一个文件,界面是从它推导出来的。

2. 加一个字段

销售想记录预算。打开 plugins/crm-starter/config/fields/crm_lead.json,追加:

{
  "code": "crm_lead_budget",
  "displayName:en": "Budget",
  "displayName:zh-CN": "预算",
  "dataType": "decimal",
  "constraints": { "required": false },
  "feature": { "sortable": true }
}

字段存在,不等于模型在用它。plugins/crm-starter/config/bindings/crm_lead.json 里绑上:

{
  "modelCode": "crm_lead",
  "fieldCode": "crm_lead_budget",
  "sequence": 15,
  "required": false,
  "visible": true,
  "editable": true,
  "displayConfig": { "sortable": true }
}

重新导入插件:

curl -s -X POST $BACKEND/api/plugins/import/import-directory-sync \
  -H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
  -d '{"path":"/app/plugins/crm-starter","conflictStrategy":"OVERWRITE"}'

两件事在你没要求的情况下发生了:

  • 数据库变了。 mt_crm_lead 多了一列 crm_lead_budget numeric。你没写任何 migration。
  • 界面变了。 刷新 CRM → 线索,表单里已经有"预算"。

3. 看着平台拒绝写入它

带预算新建一条线索:

curl -s -X POST $BACKEND/api/meta/commands/execute/crm:create_lead \
  -H "Authorization: Bearer $JWT" -H 'Content-Type: application/json' \
  -d '{"payload":{"crm_lead_company":"Contoso Ltd",
                  "crm_lead_contact_name":"Bo Chen",
                  "crm_lead_budget":50000},
       "operationType":"CREATE"}'

命令报告成功 —— "code":"0""phaseReached":"completion"。然后看那一行数据:

LEAD-20260713-002 | Contoso Ltd | budget = (空)

预算没了。 不是报错,不是告警,是被丢弃。

这是第二个理念,也是多数平台做错的地方。字段确实在模型上,但 crm:create_lead 从来没说过它接受这个字段。打开 plugins/crm-starter/config/commands/crm_lead.json

{
  "code": "crm:create_lead",
  "type": "create",
  "inputFields": [
    "crm_lead_company", "crm_lead_contact_name", "crm_lead_contact_phone",
    "crm_lead_contact_email", "crm_lead_source", "crm_lead_industry",
    "crm_lead_score", "crm_lead_campaign", "owner_type", "owner_id",
    "crm_lead_requirement"
  ],
  "permissions": ["crm.lead.manage"],
  "autoSetFields": {
    "crm_lead_code":   { "strategy": "auto_generate", "pattern": "LEAD-{yyyyMMdd}-{seq}" },
    "crm_lead_status": { "strategy": "fixed_value", "value": "new" }
  }
}

inputFields白名单,不是文档。不在上面的一律丢弃 —— 而这正是你希望发生的事,比如哪天有人往你的 API POST 一个 {"crm_lead_status":"won"}。也留意 autoSetFieldscrm_lead_code 是自动生成的,所以客户端无论传什么都定不了它。

"crm_lead_budget" 加进 inputFields,重新导入,再建一次:

LEAD-20260713-003 | Fabrikam Inc | budget = 50000.00

两份声明,两件事。模型说明这个东西是什么;命令说明什么可以被写、谁可以写、以及系统自己填什么。

4. 注意谁被允许

其实你刚才已经看到了,就在同一条命令上:

"permissions": ["crm.lead.manage"]

这不是一个界面层的检查、绕过去就没了。它是命令自己的契约,在管道里强制执行 —— 而管道是唯一的写入路径。一个人点保存、一个自动化、一个 BPM 节点、一个调用工具的 AI agent:四者都会来到这里,都会被同一行代码衡量。

没有第二条路。整个设计的要点就在这。

5. 读一遍你做过的事

每一次执行 —— 无论通过还是被拒 —— 都留下记录。

SELECT command_code, success, phase_reached, execution_time_ms, request_payload
FROM   ab_command_audit_log
ORDER  BY created_at DESC
LIMIT  3;
crm:create_lead | t | completion |  24ms | {"crm_lead_code": "LEAD-20260713-003",
                                           "crm_lead_budget": 50000,
                                           "crm_lead_status": "new",
                                           "crm_lead_company": "Fabrikam Inc", …}
crm:create_lead | t | completion |  87ms | …
crm:create_lead | t | completion | 532ms | …

管道真正接受的 payload、谁发的、耗时多久、走到了哪一阶段。你没有打开过这个开关;你也没法只对某一个调用方关掉它 —— 因为根本只有一个调用方:管道。

你刚刚证明了什么

模型是一个文件你加了个字段,列和表单跟着就有了。没有 migration,没有页面。
命令是一份契约它静默拒绝了自己从未同意接受的值 —— 并在审计里如实记下它真正接受了什么。
权限挂在命令上不是挂在按钮上。人、自动化、agent 都过同一行。
审计不是一个功能它是"只有一条入口"这件事的必然结果。

下一步

  • 创建第一个 Model —— 同样的闭环,但这次是你从零写模型,而不是改一个现成的。
  • Permission —— 五层权限,以及一个角色是怎么够到一条命令的。
  • Command Pipeline —— 你刚才在 phase_reached 里看到的那些阶段。
  • 搭建 CRM —— 七章走完,最后发布一个模块。