核心经营系统概览
规则、权限、口径存成带版本的数据,单据的判定、写入与留痕走同一套入口
核心经营系统管一笔单据从进入到留痕的全过程,也是 AI 接进经营要先打好的底子。它先把规则、权限和统计口径存成带版本的数据,一笔单据当时怎么判的、一条规则谁改的,事后都查得到。模型缺省关闭,开启后起草的每一步都走同一套权限、审批和留痕,所以 AI 能经手涉及钱的单据,又不会越过任何一道关。制度还没成文,或业务量小到靠人盯得住时,这套系统的收益抵不上搭建成本。
一笔单据怎样走
图里只有「取规则并建单」一步,把当时命中的规则行冻进了单据;其余各步在各自的位置留下可查的数据。
| 步 | 做什么 | 细节 |
|---|---|---|
| 准入判定 | 授权入口回答「这个人能不能做这个动作」,拒绝行先于允许行,没有命中就拒绝 | 谁能做什么 |
| 取规则并建单 | 按单型与时点取金额档位行,把命中的那一行快照写进单据 | 审批流转 |
| 签核 | 谁能签由签核位与资格决定,状态转移由一支纯函数判定 | 审批流转 |
| 终局写业务 | 批准之后,执行业务写入,资金流水只追加 | 资金流水与更正 |
| 追加留痕 | 每条写入命令追加一条审计事件,事件之间用哈希串成链 | 留痕与证据链 |
纯函数指同样的输入得到同样的输出,不读外部状态。哈希链指每条记录带着前一条的指纹,中间抽掉一条,校验时序号对不上。
因为档位行是建单时的快照,之后规则怎么改,已建的单都不回头变。审计拿出一张旧单,看到的是建单那天的规则,旧单也能按当时的判据重放。
查一笔单据,沿图里的步骤逐个核对,每一步都有对应的记录。
模型起草的单据也走同样的步骤,没有为模型另开的通道。它的输出只能成为待审批的草案,经过权限、审批、留痕才写入业务。

图里命中的规则带着版本号 v3,这就是建单时冻进单据的那一行;复核停在待签,系统只记停留时长,不替人批准。
版本记在各处
一份规则「每次改动都有版本」,这句话对,但版本不止一处。
| 版本 | 落在哪 | 回答什么 |
|---|---|---|
| 规则行版本 | 规则表每一行,每次变更自增,旧行保留 | 这条规则改过几次,谁改的、为什么改 |
| 签法版本与档位快照 | 审批单 | 这张单按哪一版审批链、哪一档金额线走 |
| 口径版本 | 口径定义表 | 报表上这个数按哪一版算法算出 |
| 权限集版本号 | 会话快照 | 这个会话按哪一版权限判定 |
| 投影版本 | 模型调用留痕 | 这次调用送进模型的字段是哪一版 |
要回答「这笔单据当时依据什么」,得按单据的类型去对应的一处查,没有一个全局版本号能一句话答完。各类规则按各自的节奏改,版本也各记各的。
技术选型
核心经营系统用通用、成熟的组件搭成。下表只写会影响判断的取舍。
| 层 | 选择 | 这一层承担什么 | 代价或限制 |
|---|---|---|---|
| 数据库 | PostgreSQL 16,手写 SQL,不用 ORM,只启用 pgcrypto 一个扩展 | 规则与闸放在应用绕不过的一层:约束、触发器、行级隔离、定义者函数、窄权限角色 | 一部分判定逻辑写在 SQL 里;库内与应用内各有一份解释器时,要逐键对拍 |
| 服务端 | Node.js,用 node:http 自建服务,不用 Web 框架 | 判定写成纯函数,形状靠契约校验与真库用例保证 | 路由自己维护 |
| 前端 | React 18、Vite 5、TypeScript;表格用 TanStack Table,图表用 ECharts | 现场有较老的浏览器,构建目标钉到较老的内核;ECharts 自托管,页面只加载本站脚本 | 可用的新特性受构建目标限制 |
| 契约 | zod 定义,导出 JSON Schema | 入参与出参的格式只定义一次:前端读 zod 的类型,服务端读 JSON Schema 做运行时校验 | 契约包是两端的共同依赖,改契约两端一起动 |
| 库结构变更 | 自写:顺序 SQL 文件、依赖序清单、台账 | 回滚只回代码,所以库结构变更一律只加不改;待执行的结构变更脚本是清单与台账的差集;克隆库重放两遍,第二遍零变动 | 工具自己维护,细节见发布与回滚 |
| 大模型 | 自写网关,缺省关闭,不引入模型 SDK | 进入模型的字段走白名单,输出按契约校验,每次调用留痕,失败回退规则文本 | 依赖少,功能也比成品网关少,见接入模型服务 |
| 测试 | 后端 node:test;数据库用例连一次性的真 PostgreSQL 实例 | 数据库里的触发器、行级隔离、定义者函数,靠真库用例验证 | 全量数据库用例需要能起一次性实例的环境 |
表中 Web 框架、ORM、测试运行器这三项只写它们承担的职责。
触发器是数据库在写入时自动执行的检查。行级隔离是数据库按会话里的租户过滤能看到的行。定义者函数是以函数所有者的权限执行的写入入口,业务角色不能直接写表,只能调它。
核对每一层放什么、规则改在哪,对着这张表即可;每行右侧的代价,就是可以追问的地方。
限制与不适用
- 规则与约束放进 PostgreSQL,数据库成了系统的核心,换库要重写判定。
- 新增一种判据要发版,判据种类在代码里。
- 没有全局统一的规则集版本号,「依据」分散在几处。
- 备份恢复指标与密钥管理这里不给,按企业要求在合同中约定。
- 制度还没成文、业务量小到靠人盯得住时,搭这套系统的收益抵不上成本。
与常见做法对照
约束放在哪一层,决定谁能绕过它。
| 放法 | 谁能绕过 | 代价 |
|---|---|---|
| 应用代码 | 不经过这段代码的连接 | 判据易在多处各有一份 |
| 独立的策略引擎,如 OPA | 不经引擎直接访问数据的路径 | 多一个进程,多一门策略语言 |
| 数据库内的约束与行级隔离(SZV) | 表属主、超级用户,以及带 BYPASSRLS 属性的角色 | 判定逻辑与数据库绑得更紧 |
PostgreSQL 文档写明,表属主通常不受行级安全约束,除非对表设了 FORCE ROW LEVEL SECURITY;超级用户和带 BYPASSRLS 的角色始终绕过。「应用之外的连接也要过闸」只在应用用普通角色连库时成立,它防的是代码漏写条件,不防数据库管理员。
其他做法
| 做法 | 它解决什么 | 为什么不用 |
|---|---|---|
| 规则与约束写在应用代码里 | 开发快,数据库可以换 | 应用之外的连接不经过它;同一判据在库与应用各有一份时,两份会漂。SZV 把规则与闸放进库,另用代码扫描拦截业务代码里的第二份判断 |
| 回滚时连库一起回 | 版本与数据结构保持一致 | 库里已有新数据时,回库会丢数据;每份库结构变更还要再写一份回退脚本并测试。SZV 只回代码,库结构变更因此一律只加不改 |
扫描是启发式的,拦得住照抄的判断,证明不了语义上只剩一份。
本章各页
| 页 | 回答的问题 |
|---|---|
| 审批流转 | 一张单按哪一版链走,没人签时系统怎么办 |
| 修改一条规则 | 改一条规则要经过哪几步 |
| 配置一个人的权限 | 一个人的能力由哪几层叠加 |
| 资金流水与更正 | 更正怎样不改旧行,核对怎样做 |
| 客户资料与统计口径 | 来源资料保留,报表怎样只算一次 |
| 查询与导出操作记录 | 怎样查整条经过,导出怎样限时 |
| 接入模型服务 | 网关、名册与开关 |
| 发布与回滚 | 上线出问题,怎样只回代码 |
验收时可以问的问题
- 生产备份的恢复点与恢复时间,在合同里怎样约定。
- 密钥由谁管理、怎样轮换,在合同里怎样约定。
相关页
- 让 AI 接进经营的设计:这些选择背后的原理与代价。
- 审批流转:一笔单据最先遇到的一章。
- 安全边界:防谁、不防谁的整体边界。
- 追溯一笔付款的审批:把审批规则写成表并上线的完整做法。