系统理解 245 问
把「行动系统」讲成一条能跑通的链路
这本读本用问答的形式,把 ZY Action Platform(对标 Palantir 的产品矩阵)从「用户点一下按钮」到「外部系统数据库真的被改写」的完整链路拆开。每一个回答都尽量给出源码位置,你可以自己打开代码核对。目标读者:接手本项目的中级工程师、需要向客户解释系统能力的 FDE、以及要做集成对接的外部开发者。
0怎么用这本读本
先给结论:本系统的核心不是「一个聊天框」,而是一条从触发到写回外部系统的可审计链路。如果你只记住一句话,请记住这句:
这套链路的三个关键要素是:数据源(物理连接)、本体 Ontology(业务语义层)、工作流 Workflow(执行编排)。第一次读建议按 1→7 章顺序;如果你是被某个具体问题困扰(比如「AIP 和 Foundry 的数据源到底什么关系」),直接跳到对应章节。
读本的三条约定
| 约定 | 含义 |
|---|---|
| 源码路径 | 所有路径相对仓库根目录下的 action/,例如 products/aip/nlq/orchestrator.go。行号以当前分支为准,代码重构后行号可能偏移,请以符号名检索。 |
| 状态标签 | 已实现=有真实后端代码与端到端闭环;有条件=真实但受开关/凭证/边界限制;仿真=链路真实但业务域是模拟的。 |
| 冲突处理 | 文档与代码冲突时一律以代码为准。本读本编写时逐条核对过源码,但仍可能滞后。 |
1系统总览
Q1这个项目到底是什么?一句话说清。
它是「AI 行动系统(Action Platform)」:用 AI 把「看到数据 → 做判断 → 执行动作」闭环起来。对标 Palantir 的产品矩阵,包含五个产品:LightAIP(智能分析)、LightFoundry(数据集成与本体)、LightApollo(供应链运维)、LightGotham(情报分析)、LightSwift(数据导入流水线)。技术上是一个 Go 单体仓库(monorepo),五产品共享同一套平台底座,各自独立进程、独立端口。
Q2「行动系统」里的「行动」指什么?
指对业务系统产生副作用的操作,而不仅仅是「查询和展示」。最典型的行动是:Foundry 的 write-path 把动作执行结果真实写回外部数据源(products/foundry/writepath/pipeline.go 调 conn.ExecWrite 执行真实 SQL)。其次是工作流节点调外部 REST API(call_api)、发飞书/邮件通知(send_notification)、执行 Foundry 动作(execute_foundry_action)。
Q3代码结构怎么分层?
三层:platform/ 是共享底座(config / database / logger / security / auth / rbac / audit / llm / connector / datasource / scheduler / cdc 等);products/* 是各产品业务实现(aip / foundry / apollo / gotham / swift / server / workbuddy);web/ 是 Vue3 前端。所有产品 import zy_action/platform/*,这是 Go module 层面的复用,不是微服务依赖。
Q4五产品的端口分配是怎样的?
| 产品 | 端口 | 定位 |
|---|---|---|
| AIP | 18080 | NLQ 智能查数、工作流、Copilot、知识库 |
| Foundry | 18081 | 数据集成、数据集、本体、函数、动作写路径 |
| Apollo | 18082 | 供应链规划与交付运维 |
| Gotham | 18083 | 情报分析(实体消解、知识图谱、时空分析) |
| Swift | — | 数据导入流水线(金融结算,仿真为主) |
Q5启动二进制时必须注意什么?
cwd 必须是 action/ 目录,即使二进制在 action/bin/aip.exe。原因是配置读取逻辑找的是当前工作目录下的 config.yaml。如果 cd action/bin 再启动,会读不到 config,SECRET_KEY 会退化到仓库根 .env 的首条值(实测 33 字符 vs config.yaml 的 79 字符),导致与其他产品不互认 → 跨产品 token 401、前端被登出。正确写法:cd action && ./bin/aip.exe。
Q6启动后怎么确认服务健康?
用 health 端点探测,注意绕过代理:curl -s --noproxy '*' -o /dev/null -w '%{http_code}' http://localhost:18080/health 应返回 200。五个产品同理,只是端口不同。
Q7数据库用的是什么?五个产品是共库还是分库?
优先 PostgreSQL,MVP/星上场景用小 SQLite。config.yaml 里 dbname: chatbi_action_dev,五个产品共用同一套 PostgreSQL 库(用户、对象数据、上下文都在一起)。 AIP 另有独立的 demo SQLite(./data/aip_demo_datasource.db)与元数据库(data/platform.db)。
Q8模块之间怎么通信?用不用消息队列?
两种方式:① Go Interface 直接调用(同进程内,最常用,例如工作流节点直连 AIP service,不经 HTTP 环回);② 显式 HTTP 路由(跨产品,例如 AIP 调 Foundry 的 /api/v1/ontology/semantic-search)。没有引入 Kafka/RabbitMQ 这类外部 MQ;事件走的是数据库轮询 + 内部 bus(platform/cdc/bus.go)。
Q9主力 LLM 是什么?怎么配置?
主力是 deepseek-v4-flash,embedding 用 dashscope_embedding。API Key 走 .env(deepseek / dashscope 的 key)。无 config.yaml 时 LLM key 从环境变量读取。另有 LLM 网关模块(platform/llm + model/llm_gateway.go)做统一出口。
Q10一条端到端链路长什么样?
Q11为什么叫「行动」而不是「BI」?
BI 的终点是「看得见」,本系统的终点是「动了手」并且留得下证据。第 ⑦ ⑧ 步是关键:写回是真实的,审计是强制的(writepath/pipeline.go 的 stepAudit 不可跳过)。
Q12系统里有哪些「触发源」?
六种:手动 API(service.go Run)、定时 cron(workflow/cron.go)、数据变更 CDC(platform/cdc/detector.go 轮询水位)、Foundry 对象变化事件(workflow/event.go 每 30s 拉 change-events)、webhook 入站(POST /webhooks/:name)、AI Agent 自主触发(Copilot function calling)。详见第 6 章。
Q13系统里有哪些「执行能力」(节点类型)?
九类:trigger / query_data(渲染 SQL 执行,带 RLS/CLS)/ nlq_query(走完整 NLQ 链路)/ ai_analysis / ai_generate(调 LLM)/ condition(条件分支)/ send_notification(内部/飞书/邮件)/ call_api(调外部 HTTP)/ execute_foundry_action(调 Foundry 动作)。注册表在 products/aip/workflow/nodes.go:69-91。
Q14「平台底座」到底提供了哪些能力?
| 底座模块 | 提供的能力 |
|---|---|
platform/config | 配置加载、默认值、环境变量兜底 |
platform/database | GORM 连接、自动迁移 |
platform/security | AES-GCM 加密(EncryptSecret)、JWT、国密 SM3/SM2 |
platform/auth + rbac | 认证、角色权限 |
platform/audit | 审计日志落库、归档 |
platform/connector | MySQL / PostgreSQL / SQLite 连接器 |
platform/datasource | 连接器缓存服务(ConnectorCacheService) |
platform/cdc | 变更检测 + 内部事件总线 |
platform/scheduler | 统一调度器(cron 注册) |
platform/llm | LLM 网关统一出口 |
Q15这五个产品是「一个系统切成五份」还是「五个独立系统」?
两者都对一半:代码上是一个仓库、一套底座、一次构建;运行上是五个独立进程、五个端口、可分别部署。它们共享数据库、共享 JWT 密钥(必须一致)、通过 HTTP 互调。可以理解成「一个平台 + 五个功能域」。
Q16前端是怎么组织的?
Vue3 + Vite,纯 JS(非 TS)。前端按产品前缀分流 API:Apollo 页面用 src/api/apolloClient.js(/apollo-api 前缀)、Gotham 页面用 src/api/gothamClient.js(/gotham-api)、其他产品用 src/api/client.js(/api 前缀,指向 Foundry 后端)。混用 client 会 404,这是新人最常踩的坑。
Q17新增一个前端页面要动哪几个地方?
三处:① 在 web/src/router/index.js 注册路由;② 加入对应产品的侧边栏入口(Foundry 是 web/src/views/FoundryLayout.vue);③ 选对 API client 实例。项目有硬性约定:后端路由就绪即同步补齐前端页面,避免「后端能力远超前端展示」。
Q18「Connector 工厂 allow-list」是什么?
默认只放行 MYSQL / SQLSERVER / POSTGRESQL 三种类型,其他类型需要显式调用 AllowAdvancedTypes("SQLITE") 授权。这是安全上的「白名单」设计,避免用户配了任意连接串导致 SSRF 或误连。AIP 演示数据靠 bootstrap 启动时用这个显式授权重建 demo SQLite(orders/customers/products 表)。
Q19系统的「语义层」在哪?
在 Foundry 的本体(ontology)里。wiki/design/design_overview.md 明确定位:Foundry 是语义层地基,AIP 只消费不拥有。AIP 端的本体表只是 MVP 最小实现(用于 prompt 上下文),真正的对象类型/属性/链接/动作定义在 Foundry。
Q20如果只给我 30 分钟了解这个系统,看什么?
顺序:action/config.yaml(看有哪些开关)→ platform/model/models.go(看核心数据模型)→ products/aip/workflow/nodes.go(看能力清单)→ products/foundry/writepath/pipeline.go(看「行动」怎么落地)→ 本读本第 7 章。
2数据源与连接器
Q21AIP 的数据源存在哪张表?
表名 data_sources,模型定义在 platform/model/models.go:19-26:
type DataSource struct {
ID uint `gorm:"primaryKey;autoIncrement"`
Name string `gorm:"uniqueIndex;not null"` // 全局唯一名
Type string `gorm:"not null"` // MYSQL/POSTGRESQL/SQLITE...
ConnectionConfig string `gorm:"type:jsonb;not null"` // 含加密后的 password
CreatedAt time.Time
UpdatedAt time.Time
}
注意 ConnectionConfig 里的 password 是 AES-256-GCM 密文(crypto.EncryptSecret,masterKey 由 SECRET_KEY 派生),响应侧统一脱敏成 "****"(convertModelToSchema)。
Q22Foundry 的数据源是哪张表?
同一张表:platform/model/models.go 的 DataSource → data_sources。也就是说,AIP 和 Foundry 的数据源注册共用同一份数据模型和同一张表。这是本读本要澄清的第一个大误解——它们不是两套独立的东西,而是同一套底座上的两个产品的视图。
Q23那为什么会有「AIP 数据源」和「Foundry 数据源」的说法?
因为两个产品的前端页面、租户语境、演示数据不同(AIP 里有 aip_demo_warehouse,Foundry 里有 foundry_demo_warehouse),但底层落的是同一张 data_sources 表。名字不同只是演示命名习惯,不是两个注册中心。OAG 的 data_source_map 就是为了把 AIP 侧的名字翻译成 Foundry 侧的名字(见 Q63)。
Q24连接器(connector)代码在哪?支持哪些数据库?
platform/connector/{mysql,postgresql,sqlite}.go(外加 SQLServer)。统一接口方法:TransformSQL(把平台 SQL 翻译成目标方言)、ExecuteSQL(执行查询)、ExecWrite(执行写入,返回 RowsAffected)。
Q25platform/datasource 是干什么的?
它是连接器缓存服务(ConnectorCacheService)。业务代码通过 connectorCache.GetConnector(ctx, dataSourceName) 按数据源名拿到可用的连接器实例,避免每次查询都重新建连接。AIP 执行 SQL 和 Foundry 查询本体数据都走它。
Q26表结构元数据(table schema)存在哪?
TableSchema / ColumnSchema(platform/model/models.go:30-60),表名 table_schemas。这是「导入元数据」的产物:调用 POST /api/v1/datasources/:id/import-metadata 后,系统把物理表的表名/列名/类型抓下来存进去,供 Text2SQL 的 prompt 使用。
Q27导入元数据时为什么要先清理旧数据?
因为残留的旧表名会误导 RAG 和 Text2SQL(明明表已经删了,还在 prompt 里推荐)。所以导入前需要按 table_schema_id 清理旧的 columns / tables。这是实测踩过的坑(见 AGENTS.md 常见坑清单)。
Q28数据源的密码是怎么保护的?
写入时 crypto.EncryptSecret(AES-256-GCM)加密后落库;读取时由 datasource 服务/连接器缓存解密;存量明文通过 TryDecryptSecret 兼容;响应侧一律脱敏为 "****",绝不回显明文。约定写在 platform/model/models.go 的字段注释里。
Q29数据源有哪些 API?
AIP 后端实测通过的:GET|POST /api/v1/datasources、GET|PUT|DELETE /api/v1/datasources/:id、POST /api/v1/datasources/:id/test(连通性测试)、POST /api/v1/datasources/:id/import-metadata(导入元数据)。
Q30「数据源对应数据接口,写入 SQL 数据表,通过查询相关数据找到数据接口进行连接」这句话怎么理解?
这是项目约定里对「数据源接入」的描述:一个数据源 = 一个数据接口 = 一张/一组 SQL 数据表。接入流程是:先在 data_sources 注册连接(拿到 ID)→ 导入元数据(拿到 table_schemas)→ 通过查询元数据找到可用的表/接口 → 业务侧(本体/数据集/NLQ)按名字引用这个数据源。示例数据源:sales_data_warehouse(POSTGRESQL,库 chatbi_forge_dev)。
Q31Foundry 的「数据集(Dataset)」和数据源什么关系?
数据集是数据源的下游物化。products/foundry/dataset/models.go:71-90 定义 Dataset → 表 fd_datasets。当 source_type=platform_ds 时,source_ref 里存 {ds_id, query}(:120-126)——即「从哪个数据源、用什么 SQL 拉」。dataset/service.go:400-447 调 connFor(ds_id) 拿 platform connector,执行 query,把结果落到 ds_* 行表里。
Q32为什么不直接查业务库,要先落数据集?
三个理由:① 隔离性——业务库压力大,物化后查询更稳;② 可追溯——数据集有版本、血缘(lineage);③ 可治理——质量检查、打标、权限都作用在数据集上,而不是直接怼生产库。
Q33SQLite 数据源有什么坑?
必须先 ensureDir 创建目录,否则报 unable to open database file。另外 GORM struct 需要固定 TableName(),否则 DropTable 与 GORM 推导的表名不一致。
Q34数据源名为什么要求唯一?
因为上层大量逻辑是按名字引用的:OAG 的 data_source_map 按名字翻译、GetConnector(ctx, name) 按名字取连接器、NLQ 意图识别输出 dataSourceName。名字重复会导致解析歧义,所以 Name 字段加了 uniqueIndex。
Q35「多产品分端口代理」和前端有什么关系?
Vite 开发服务器(5173)代理需要按产品前缀分流:/api → 18081(Foundry)、/apollo-api → 18082(Apollo)、/gotham-api → 18083(Gotham)。配错了请求会打到错误后端报 404。生产环境由网关做同样的分流。
Q36数据源测试(test 接口)做了什么?
拿配置里的连接串尝试建连 + 执行一句轻量探测 SQL,返回连通性结果。不修改任何数据。它是接入数据源前的第一道验证。
Q37表/列元数据在哪个环节被使用?
两处:① Text2SQL prompt 的 {table_schema} 段(products/aip/nlq/text2sql.go:148-168);② RAG 检索(把表/列/描述向量化后找回相关上下文)。OAG 命中时,本体语义会并联物理 schema 一起注入(orchestrator.go:259-263)。
Q38数据源连接失败会怎样?
分场景:NLQ 查询会抛错中断返回给用户;OAG 语义检索失败只 warn 并降级到本地本体/RAG,不阻断主链路(orchestrator.go:248-273);工作流里 call_api 节点失败是非致命的(失败不阻断流程)。
Q39为什么 OAG 需要一个「数据源名映射表」?
因为 AIP 和 Foundry 的演示数据源命名不同(如 aip_demo_warehouse vs foundry_demo_warehouse),而 OAG 的语义检索要求在 Foundry 侧按数据源过滤。映射表 data_source_map 就负责把 AIP 侧名字翻译成 Foundry 侧名字,避免「AIP 配了数据源但 OAG 查不到」。
Q40列名映射 column_map 解决什么问题?
口径公式在 Foundry 本体里写的是 Foundry 列名(如 amount),但 AIP 实际执行的表列名可能不同(如 sales_amount)。若不做整词重写,LLM 拿到的口径公式与实际表结构对不上,生成的 SQL 会报「列不存在」。column_map 在注入前把 Foundry 列名整词替换成 AIP 列名(oag/service.go:358 applyColumnMap)。
3本体(Ontology)
Q41什么是本体?在这个系统里它能干什么?
本体是业务语义层:把物理表抽象成「对象类型(Object Type)」,把列抽象成「属性(Property)」,把表间关系抽象成「链接(Link Type)」,把可执行的操作抽象成「动作(Action Type)」。用户说「给这个订单加急」时,系统理解的是「对 Order 对象执行 expedite 动作」,而不是「UPDATE orders SET ...」。
Q42Foundry 的本体模型定义在哪?
products/foundry/ontology/models.go。OntologyObjectType(表 ontology_object_types,:90-111)含 17 列完整版定义,关键字段 DataSourceID(指向数据源)和 BaseTable(指向物理表)。OntologyProperty(:140-163)用 MappedColumn 映射到物理列。
Q43AIP 的本体模型定义在哪?
platform/model/ontology.go。:29 定义 ontology_object_types(MVP 9 列),:54 定义 ontology_properties(13 列),另有 ontology_link_types(:86)。AIP 源码注释自述:「MVP 自建最小本体表,不跨进程调用 Foundry 本体」(platform/model/ontology.go:12-13、products/aip/ontology/service.go:1-5)。
Q44所以 AIP 本体和 Foundry 本体是两套?这不是设计缺陷吗?
是两套,但有明确的设计意图:Foundry 是语义层地基,AIP 只消费不拥有(wiki/design/design_aip.md:67「AIP 不拥有语义层」)。AIP 自建的本体表是 MVP 的权宜实现,用于在没有 Foundry 时也能给 prompt 注入本体上下文(降级路)。但它确实带来一个隐患:表名同名、列集互斥、无外键、ID 不共享。共库(PG)时,AIP 和 Foundry 的同名表会互相干扰。项目已有防御措施(见 Q47)。
Q45本体是怎么绑定到物理数据的?
两条链:① 对象类型 → DataSourceID + BaseTable;② 属性 → MappedColumn。查询执行时 products/foundry/query/translate.go:148-149 用 ObjectType.DataSourceID 调 connProvider.GetConnector 拿连接器,把本体查询翻译成物理 SQL 执行。
Q46本体查询的入口 API 是什么?
对象实例查询:POST /api/v1/objects/:id/query(AIP 的 automation catalog 里封装成工具 foundry_query_object_data,products/aip/automation/catalog/foundry.go:98-118)。语义检索:POST /api/v1/ontology/semantic-search(products/foundry/semantic/search.go)。变更事件:GET /api/v1/ontology/change-events。动作执行:POST /api/v1/ontology/actions/:name/execute。
Q47同名表互相干扰的隐患,现在怎么防的?
项目记录在 wiki/dev_memory/db_shared_analysis.md(§1.3 行 141-153、结论表行 419),把它称为「已加防御的定时炸弹」。防御手段包括:ontology_properties 的数据类型对齐(ontology_properties_data_type.md),以及规划中的 aip_ 前缀隔离方案。短期靠约定与字段兼容共存,长期应做表名前缀隔离。
Q48本体里的「链接(Link Type)」有什么用?
描述对象之间的关联(如 Order ↔ Customer 的 belongs_to)。AIP 的本地本体降级路会把链接连同 JOIN 条件一起渲染进 prompt(ontology/service.go:386-494),让 LLM 知道多表怎么 JOIN。
Q49「指标(Metric)」在本体里是什么?
带口径公式(formula)和维度(dimensions)的度量定义。OAG 会把指标连同 formula 一起注入 prompt,让 LLM 生成的 SQL 符合业务口径,而不是自己瞎编聚合逻辑。这是 OAG 最有价值的注入内容之一。
Q50本体有版本管理吗?
有。基础版 demo 里包含「本体版本审批」环节(见 docs/ai-action-demo.md),即本体的变更可以走审批流程,避免线上语义被随意改动。
Q51本体和「函数(Function)」什么关系?
函数是可复用的计算逻辑,挂在本体语境里。products/foundry/ontology/function.go:197-220 的 FunctionService.Test 调用 workflow.EvalExpression(fn.Body, vars) 求值;用户函数可递归(由 MaxUserFunctionDepth 限深,:227-251)。
Q52函数用什么语言/运行时执行?
不是 JS / Python / WASM,而是自研的表达式解释器。products/foundry/workflow/expression.go:1-20 定义语法({{var}} == value、算术 + - * /、逻辑 && || !、函数调用),:340-580 是手写的 tokenizer + Pratt parser。这是一个刻意「不过度封装」的选择——用最简单的表达式满足条件判断与轻计算。
Q53「动作(Action Type)」有哪些种类?
edit_type ∈ create / modify / delete / link,由 products/foundry/writepath/pipeline.go:154 的 Executor.Execute 按 mode ∈ VALIDATE / RUN / ASYNC / VALIDATE_AND_EXECUTE 分派。四种编辑类型覆盖了对象生命周期的增改删与建联。
Q54动作执行后的副作用是什么?
两个:① 真实写回目标数据源(pipeline.go:402 conn.ExecWrite);② 生成 object_change_events(pipeline.go:1286-1342 publishChangeEvent),供 AIP 的工作流事件触发器轮询消费。这是「写回 → 触发下游」闭环的关键。
Q55本体数据能被 AIP 直接当数据源查吗?
不能,不存在「ontology-backed datasource」。AIP 的 executor.go 只走 connector.ExecuteSQL,没有把 ontology 当一个数据源类型。AIP 要查 Foundry 本体数据,得走 HTTP:OAG 的语义检索,或 automation 的 /objects/:id/query。
Q56那为什么不干脆把本体做成数据源,让 NLQ 直接查?
架构上这是「语义层与物理层分离」的取舍。好处是职责清晰(Foundry 管语义,AIP 管消费)、权限边界清楚(本体查询走 Foundry 的对象级权限)。代价是多了 HTTP 一跳。项目选择了前者,并在文档里明确 AIP「不拥有语义层」。
Q57本体的对象级权限在哪校验?
在 Foundry 侧。OAG 检索时会把用户 token 透传(或配 service_token 用服务账号),Foundry 的 objectProvider 做对象读权限过滤。注意:若配了 service_token(Foundry admin JWT),admin 通配全部对象读权限,对象级过滤实际上不生效——文档已明确提示这一点,推荐用 security.username_fallback=true 的同名用户对齐方案替代。
Q58本体里的属性数据类型需要注意什么?
ontology_properties 的 data_type 必须与物理列类型对齐,否则查询翻译会出错。项目专门有 wiki/dev_memory/ontology_properties_data_type.md 记录这个坑。
Q59本体在 AIP 里还有别的用途吗?
有:作为 NLQ 的 prompt 上下文注入源(降级路),以及在 OAG 不可用时提供对象-属性-链接文本。输出内容包含对象、属性、链接(含 JOIN 条件),由 ontology/service.go:386-494 BuildOntologyContext 生成。
Q60本体的「变更事件(change events)」是什么机制?
Foundry 在对象变更时把事件写入 object_change_events。AIP 的 EventScheduler 每 30 秒拉取 GET /api/v1/ontology/change-events,游标存在 system_settings.workflow_event_cursor,按 trigger_config.filters 匹配后触发 trigger_type=event 的已发布工作流(products/aip/workflow/event.go:31-227)。这是「数据变了自动干活」的核心。
4OAG 本体增强生成
这一章回答你最初的第二个问题:「OAG 是 AIP 参考 Foundry 本体真实情况来做查询交互的吧,有用上吗?」
结论:设计理解正确,而且已经真正接线在用,不是空壳。但要补三个精确认知:① 它注入的只有本体元数据(对象/属性/指标口径/动作名),不含实例数据行;② 它注入的位置是 NLQ 的 Text2SQL 生成步,不是意图识别也不是结果解释;③ 代码默认 enabled=false,本仓库的 config.yaml 已显式开启为 true。
Q61OAG 是什么缩写?
Ontology-Augmented Generation(本体增强生成),代码注释里也写作「Ontology-Aware Generation」。核心思想:LLM 直接看物理表结构容易写错 SQL(不知道业务口径、不知道对象关系),于是在生成 SQL 前,先把 Foundry 本体的语义(对象类型、属性映射、指标口径公式、可用动作)检索出来,作为结构化上下文注入 prompt。
Q62OAG 的配置在哪?有哪些 key?
配置段 foundry.oag,结构体 OAGConfig(platform/config/config.go:84-109),默认值在 :1120-1131:
| key | 默认值 | 作用 |
|---|---|---|
enabled | false | 对象路总开关 |
base_url | "" | 空则复用 foundry.base_url |
service_token | "" | 服务账号 JWT(有 OAG_SERVICE_TOKEN 环境变量兜底) |
max_tokens | 2000 | 注入 token 预算 |
max_objects | 20 | TopN 对象数上限 |
max_props_per_object | 8 | 每对象属性上限 |
max_metrics_per_object | 5 | 每对象指标上限 |
timeout_ms | 3000 | 单次语义检索超时 |
data_source_map | {} | AIP→Foundry 数据源名翻译 |
column_map | {} | Foundry 列名→AIP 列名整词重写 |
Q63本仓库的配置和默认值一样吗?
不一样,有三处偏差(config.yaml:74-85):enabled: true(默认 false,即本仓库已开启);max_tokens: 20000(默认 2000 的 10 倍);并显式配置了 data_source_map: {aip_demo_warehouse: foundry_demo_warehouse} 和 column_map: {amount: sales_amount}。所以在本仓库里 OAG 是生效状态。
Q64OAG 是怎么装配进链路的?
启动时 server.go:359 buildOAGServiceFromConfig → :365-380 NewOrchestratorWithOAG(..., oagSvc, cfg.Foundry.OAG.Enabled)。若 enabled=false,该函数返回 nil, nil(:1357-1359),即 oagSvc == nil,行为与未接入完全一致。
Q65OAG 的上下文从哪来?
主路走 HTTP:products/aip/oag/client.go:202-239 Search → POST /api/v1/ontology/semantic-search,由 Foundry 侧的 products/foundry/semantic/search.go 处理,objectProvider 就是 foundry/ontology.OntologyService.ListObjectTypes / GetObjectType(search.go:122-125, 298)。
Q66OAG 注入的具体内容是什么?
只有本体元数据,没有实例数据行。包括:object(name / base_table / actions / object_type_id)、metric(口径公式 formula + dimensions)、property(mapped_column / data_type)。组装流程在 oag/service.go:114-165 BuildOAGContext:按对象归组(groupByObject:206)→ 裁剪(trimObject:266)→ 列名重写(applyColumnMap:358)→ token 预算贪心(renderWithBudget:425)→ 渲染(renderObject:447)。
Q67为什么不含实例数据行?
两个原因:① token 预算——实例数据量不可控,塞进 prompt 会爆 token;② 职责划分——「查什么数据」交给 LLM 生成的 SQL 去查,OAG 只负责「让 LLM 知道怎么问才对」。这是 prompt 工程的克制设计。
Q68OAG 注入到 NLQ 的哪一步?
Text2SQL 生成步,不是意图识别、也不是结果解释。OAG 文本写入 ragContext,命中时与物理 Schema 并联(orchestrator.go:259-263),再由 text2sql.go:148-168 渲染进 prompt 的 {table_schema} / {{.TableSchemas}} 段。模板定义在 prompt/templates.go:30-50,其中含一条约束:「本体语义 OAG 注入内容只是口径参考」(:39)——即告诉 LLM 别把语义描述当表结构照抄。
Q69算力上有几条路?优先级是什么?
三段式优先级(nlq/orchestrator.go:241-310):① OAG 对象路(启用且命中)→ ② AIP 本地本体 → ③ RAG 多路。OAG 失败只 warn 降级,不阻断主链路(:248-273)。
Q70降级路(本地本体)读的是哪张表?
读 AIP 本地表 ontology_object_types / ontology_properties / ontology_link_types(platform/model/ontology.go:41,70,86),由 ontology/service.go:386-494 BuildOntologyContext 输出对象-属性-链接(含 JOIN 条件)文本。注意:这是 AIP 自己的 MVP 本体表,不是 Foundry 的。
Q71OAG 未装配时,那几个动作代理端点会怎样?
返回明确的 404(oag_handlers.go:28-30),语义是「能力未装配」。这是刻意设计——让调用方明确知道是「没开这个功能」而不是「服务挂了」。
Q72改 OAG 配置能热生效吗?
不能。base_url 与启用状态在构造期冻结(server.go:358 注释),热重载不生效,必须重启进程。
Q73service_token 有什么安全影响?
若配置了 service_token(Foundry admin JWT),client.go:131-137 会优先用它透传,绕过用户 token。因为 admin 通配全部对象读权限,对象级过滤实际不生效——等于所有 AIP 用户都能看到全部本体。文档明确提示了这一点,推荐改用方案二。
Q74「方案二」是什么?
security.username_fallback=true:AIP 用户与 Foundry 同名用户自动对齐(如 AIP admin ↔ Foundry admin),无需额外 token,权限按同名用户自然生效。config.yaml:22-30 配置了它,注释里称方案二为「推荐」、service_token 为「过渡」。
Q75OAG 相关文档在哪?
最全面的是 wiki/docs/aip/oag.md(含契约、裁剪逻辑、降级矩阵),另有 wiki/docs/aip/API/oag-API.md、wiki/docs/aip/ontology_integration.md;故事站 docs/story/aip/oag-object-path.html;评审 wiki/code_review-v5/aip/oag.md;记忆 wiki/dev_memory/oag_object_path.md;变更 wiki/changelog/2026-08-10-*-aip-oag-*.md;测试 wiki/testing_result/AIP/v4/oag.md、test/oag_acpt/、wiki/testing_plan/sence/s12_oag_retest.py。
Q76一句话总结 OAG。
「已落地的本体语义增强 Text2SQL」:读本体元数据(不读实例),主路经 Foundry HTTP 语义检索、降级路读 AIP 本地本体表,注入 NLQ 的 SQL 生成 prompt;默认关闭,需 column_map 才能保证口径公式列名正确。
5NLQ 智能查数链路
Q77NLQ 全流程有哪几步?
Q78意图识别输出什么?
输出 dataSourceName(要查哪个数据源)等信息(products/aip/nlq/orchestrator.go:216)。这一步决定了后面用哪个连接器。
Q79Text2SQL 是真调 LLM 还是规则?
真调 LLM。text2sql.go:113-186 把表结构、字典、OAG 语义一起塞进内置 prompt,调 LLM 生成 SQL,并在 prompt 里标注目标方言(MySQL / PostgreSQL / SQLite)。
Q80生成的 SQL 怎么执行?
executor.go:70-115 ExecuteQuery:connectorCache.GetConnector(ctx, dataSourceName) 拿连接器 → conn.TransformSQL 做方言转换 → conn.ExecuteSQL 执行。整个过程会叠加 RLS/CLS 权限裁剪。
Q81NLQ 的 API 是什么?
POST /api/v1/chat(NLQ 查询)。另有审计日志 GET /api/v1/audit/logs,每次查询都会落 nlq_query_log 便于回溯。
Q82NLQ 和工作流的 nlq_query 节点什么关系?
工作流节点 nlq_query 是复用同一条 NLQ 链路作为一个可编排的步骤。所以你在工作流里也能「用自然语言查数」。这是「一次实现、两处复用」。
Q83NLQ 的失败模式有哪些?
常见:SQL 语法错误(LLM 生成偏差)、列不存在(口径公式与实际表不符,需 column_map)、数据源连不上、权限拒绝(RLS/CLS)。前两类可以通过 OAG + 元数据治理显著降低。
Q84结果解释为什么不在 OAG 注入范围?
因为 OAG 解决的是「怎么查对」而不是「怎么讲懂」。结果解释属于后置环节,靠 LLM 直接读查询结果生成自然语言,不需要本体元数据(那时的数据已经在手上了)。
Q85RAG 在这条链路里干什么?
检索与问题相关的表/列/文档上下文,作为 OAG 与本地本体都未命中时的兜底。知识库(/knowledge)保存即切分向量化,向量库现用 ChromaDB。
Q86embedding 用什么?
dashscope_embedding(阿里云 dashscope),与主力 LLM deepseek-v4-flash 分属不同供应商。
Q87NLQ 的上下文预算怎么控制?
靠 OAG 的 max_tokens + 贪心填充(renderWithBudget:425,按 score 排序,绝不超预算)。本仓库把预算放大到 20000,说明演示场景愿意用更多上下文换准确率。
Q88如果 OAG 和本地本体都不可用会怎样?
降级到 RAG 多路检索,链路仍然可用,只是 SQL 质量下降。三段式降级保证了「永不阻断」。
6工作流与触发
Q89工作流的定义模型在哪?
products/aip/workflow/definition.go。TriggerConfig.Type ∈ manual | cron | webhook | event(:170-201)。工作流有「草稿 / 已发布」状态,发布期会做强校验(见 Q100)。
Q90工作流引擎怎么执行 DAG?
products/aip/workflow/engine.go:按拓扑序执行节点(:112),失败重试 2 次,支持条件分支路由。这是最朴素的 DAG 执行器,没有引入外部工作流引擎。
Q91节点类型总共几类?注册在哪?
九类,注册表 products/aip/workflow/nodes.go:69-91,常量定义 nodetypes.go:22-108:trigger / query_data / nlq_query / ai_analysis / ai_generate / condition / send_notification / call_api / execute_foundry_action。
Q92query_data 节点做什么?
渲染 SQL 模板并执行,自动叠加 RLS(行级)/ CLS(列级)权限裁剪。这保证工作流里的查询也不会越权。
Q93send_notification 支持哪些渠道?
三渠道:internal / feishu / email(nodes.go:363-432)。飞书走 larksuite SDK,邮件走 SMTP。由 server 注入 feishuNotify / emailNotify 实现。未配凭证时节点直接失败(不做静默吞错)。
Q94call_api 节点怎么调外部接口?
nodes.go:468-514 callAPINode:自由 URL 的 HTTP 请求,15 秒超时,响应限 1 MiB。失败非致命(不阻断后续节点)。注意:没有连接器抽象,是直接 HTTP——这是「简单实现不过度封装」的取舍。
Q95execute_foundry_action 节点做什么?
nodes.go:544-619:经 OAG HTTP 调 Foundry 的动作执行端点 POST /api/v1/ontology/actions/:name/execute。这是工作流「动手」的主要方式——把编排的决策落到 Foundry 的写路径上。
Q96手动触发走哪个 API?
service.go:342 Run(...) → runWorkflow(ctx, wf, "manual", ...)。即工作流有一个「立即运行」的入口。
Q97cron 触发怎么实现的?
workflow/cron.go:108-160 解析 tc.Cron 表达式,注册到平台统一调度器(platform/scheduler),无统一调度器时兜底用 robfig/cron。这体现了底座复用。
Q98CDC 触发和 Foundry 事件触发有什么区别?
CDC:platform/cdc/detector.go 是通用机制——轮询 watcher 表的 COUNT(*) 或水位字段,比对变化后 bus.Publish(bus.go:63 Subscribe)。适合任意数据库表的变更监听。
Foundry 事件:workflow/event.go:31-227 是专用机制——每 30s 拉 Foundry 的 change-events 接口,按本体对象变更触发,游标持久化在 system_settings.workflow_event_cursor。
Q99webhook 入站的安全措施?
handler.go:60 public.POST("/webhooks/:name") → service.go:346 RunWebhook,支持 X-Webhook-Secret 校验。这是外部系统「主动推」进本系统的入口,必须校验 secret 防止被随意触发。
Q100发布工作流时有哪些强校验?
两条硬规则(service.go:282-296):① webhook 工作流禁用 execute_foundry_action——因为 webhook 是外部可触发的,不能让外部随意改数据;② 禁止 call_api 引用 trigger 模板——防止把外部输入直接拼进 URL 造成 SSRF/注入。这是很重要的安全边界设计。
Q101AI Agent 也能触发工作流吗?
能。products/aip/automation/service.go 的 Copilot 用 LLM function calling,工具有 create_automation / run_automation_now(tools.go:196, 649)。即「LLM 自己创建并立即触达工作流」。另有 products/aip/agent/agent.go 的规则式 Planner。
Q102上游节点算不算触发源?
不算。engine.go:112 按 DAG 拓扑序驱动,上游节点是「执行依赖」而不是「触发源」。触发源是 DAG 的根(trigger 节点对应的四类配置)。
Q103工作流的执行记录在哪看?
有 executions 记录(MCP 工具 workflow_executions / workflow_execution_detail 可查)。每次执行有状态、日志、节点级明细。
Q104条件分支(condition)用什么语法?
用 Foundry 同款表达式解释器(products/foundry/workflow/expression.go):{{var}} == value、算术、逻辑 && || !、函数调用。全平台统一一套表达式语法。
Q105ai_analysis 和 ai_generate 的区别?
两者都调 LLM。ai_analysis 偏「分析已有数据」(输入上下文 + 数据 → 洞察);ai_generate 偏「生成文本内容」(如生成报告段落、通知文案)。分别对应不同的 prompt 模板与输出处理。
Q106工作流幂等性怎么保证?
节点级的重试配合 Foundry 写路径的幂等登记(writepath/idempotency.go 的 Begin/Finish)。即「重试不会重复写」由写路径兜底,而不是靠工作流引擎自己保证。
Q107一个典型工作流长什么样?
Q108工作流能调其他产品的能力吗?
能。通过 call_api 节点直调其他产品的 HTTP 接口,或通过 automation 的 apiclient(products/aip/automation/apiclient/client.go,通用跨产品客户端)。AIP 启动时装配了 apiClients["foundry"/"apollo"/"gotham"/"aip"](server.go:505-570),base URL 来自 config。
7与外部系统交互
这一章回答你最初的第三个问题:「本系统怎么和外界交互?是通过 Foundry/事件/数据触发调用里面工作流、执行里面 API、调用外部接口吗?还是直接写到客户系统数据库?」
答案:两条路都真实存在,且是你的理解 + 一个补充。触发侧确实是「事件/数据/定时/人工/webhook/Agent」六路;执行侧既能调外部 API(call_api 节点、自由 URL),也能直接写客户系统数据库(Foundry write-path 的 ExecWrite 执行真实 SQL)。这不是二选一,是两把工具都在。
Q109系统「对外」的交互可以分成几个方向?
四个方向:
① 系统读外部(数据源拉数、同步、CDC 监听);
② 系统写外部(write-path 写回业务库、call_api 调外部 REST);
③ 外部驱动系统(webhook 入站、MCP 调用、外部 API 调用本系统);
④ 系统主动通知(飞书、邮件、内部通知)。
Q110「直接写到客户系统数据库」这条是真的吗?
真的。products/foundry/writepath/pipeline.go:402:rows, err := conn.ExecWrite(ctx, sqlStr, args...);连接器层 platform/connector/postgresql.go:249-259(另有 MySQL / SQLite)确实执行 db.Exec 并返回 RowsAffected。也就是说,动作执行的结果会真的落到目标数据源(可能是客户的业务库)。
Q111直接写外部库之前要过哪些关?
完整安全流水线(writepath/pipeline.go):Schema 校验 → 逐动作 RBAC(stepRBAC)→ 记录级 RLS(stepRecordScope)→ 属性级三分权限(stepPropertyPermission)→ 强制审计(stepAudit)→ 幂等登记(Begin/Finish,idempotency.go)→ 乐观锁冲突检测(409,pipeline.go:414-443)。审计步骤不可跳过。
Q112「属性级三分权限」是什么?
对属性的读 / 写 / 编辑三分:能读不一定能写,能写不一定能编辑。这是比传统 RBAC 更细的粒度,防止「有权限的人顺手改了不该改的字段」。
Q113并发的两个动作会互相覆盖吗?
不会。pipeline.go:414-443 用乐观锁检测冲突,冲突时返回 409,让调用方重试。这是「检测冲突而非加锁」的取巧做法——没有分布式锁的复杂度。
Q114写完之后怎么通知下游?
pipeline.go:1286-1342 publishChangeEvent 生成 object_change_events。AIP 的事件调度器轮询到后可以触发工作流。这就是「写回 → 触发下游」的闭环,无需外部 MQ。
Q115除了写库,还能怎么调外部系统?
三种:① 工作流 call_api 节点(自由 URL HTTP,15s 超时,1MiB 响应限制,失败非致命);② execute_foundry_action 走 Foundry 动作端点;③ 自动化 apiclient 调其他产品的 REST 接口目录。
Q116有「连接器抽象」来统一外部接口调用吗?
没有。外部 REST 调用就是直接 HTTP(call_api 节点),没有做 connector 抽象。数据库写回走的是 platform/connector。这是一个有意的取舍:数据库连接需要统一方言与安全(因为要执行 SQL、要加密凭证),而外部 REST 差异太大,抽象收益低于成本。
Q117飞书/邮件通知是真的发出去吗?
真的发。飞书走 larksuite SDK(WebSocket 长连接接入飞书开放平台,配置在 config.yaml 的 feishu: 段),邮件走 SMTP。但未配凭证时节点直接失败,不会假装成功。
Q118飞书有哪些相关 API?
GET|POST /api/v1/feishu/status|push(状态查询与主动推送)。另有 GET|POST /api/v1/email/status|test(SMTP 邮件)。
Q119外部系统怎么主动调用本系统?
三种入口:① 常规 REST API(带 JWT);② POST /webhooks/:name(带 X-Webhook-Secret,无需 JWT,但有发布期安全限制);③ MCP 接入(经 products/server 的 MCP Hub,见第 8 章)。
Q120「行动系统」和传统「系统集成(EAI)」的区别?
传统 EAI 是「数据搬运 + 格式转换」,本系统多了语义层和决策层:本体让动作有业务含义(不是裸 SQL),LLM 让规则能自然语言表达(不是硬编码 if-else),审计让每次行动可追责。所以它更像「AI 决策 → 受控执行」而不是「数据管道」。
Q121「仿真」和「真实」的边界在哪?
真实:Foundry 写路径(真实 SQL 写真实数据源)、工作流引擎、通知(配了凭证就真发)、NLQ、OAG、权限与审计。
仿真:Swift 的 GAC 网关(products/swift/gac/client.go:4-140 仿真网关)、hcc/compliance.go:41 的名单 mock;Gotham 默认 disk JSON 落盘(config.yaml:51-54)。即「链路是真的,业务域数据是模拟的」。
Q122为什么 Swift/Gotham 是仿真?
因为这两个业务域(星上金融结算 / 情报分析)无法在真实环境验证,用仿真可以完整走通工程链路并演示,同时避免合规风险。这是「先仿真、后真实」的路线(AGENTS.md 里 Swift 明确写了「先仿真」)。
Q123系统如何保证「外部调用不越权」?
三层:① 调用前——RBAC/RLS/CLS/PEP 校验;② 调用中——连接器只开放白名单类型,SQL 经 TransformSQL 规范化;③ 调用后——强制审计落 audit_logs。另外 ABAC/PEP 的默认语义是放行,deny 必须显式配置(这是项目的硬约定,新功能不得改变)。
Q124「写回」会不会误伤生产库?
有几个防线:动作的 mode 支持 VALIDATE(只校验不执行)与 VALIDATE_AND_EXECUTE;RLS 的 stepRecordScope 限制影响范围;乐观锁防并发覆盖;审计留痕可回溯。上线前建议用 VALIDATE 模式先验证。
8MCP 与本地 AI 接入
Q125products/server 是干什么的?和产品包什么关系?
它是云端 Hub,给「本地 AI 行动平台客户端」提供 MCP 接入服务。与本地 AI action 产品包无关,独立部署、不进本地包(products/server/README.md)。
Q126MCP 的协议实现是什么?
products/server/mcp.go:GET /action/mcp 返回 SSE event: endpoint;POST /action/mcp 走 JSON-RPC 2.0,用 Bearer=<client_key> 鉴权(可选白名单)。服务端经 WebSocket rpc_request 帧把请求转发给在线的 AIP 实例,再把回包还原(mcp.go:96-198)。本地实例通过 /action/ws 出站注册。
Q127为什么 MCP Hub 要中继而不是直连?
因为本地 AIP 实例可能在 NAT 后面、没有公网入口。让它主动出站到一个 WebSocket Hub,外部 MCP 客户端就能借 Hub 中继到它。这是内网穿透的常见做法,无需开放入站端口。
Q128MCP 暴露了哪些工具?
六个(products/aip/workbuddy/tools.go:35-91):datasource_list(列数据源)、nlq_chat(发起 NLQ 查数)、workflow_list(列工作流)、workflow_run(触发工作流,真实副作用)、workflow_executions(执行记录)、workflow_execution_detail(执行详情)。
Q129MCP 工具调用是走 HTTP 环回吗?
不是。工具直连 AIP 的 service 层(tools.go:5-11),同进程内 Go 调用,不经 HTTP 环回。这样更快也更简单。
Q130tools/list 和 tools/call 在哪实现?
products/aip/workbuddy/server.go:118(tools/list)、:120(tools/call)。
Q131「本地 AI 客户端」能通过 MCP 干什么?
能列数据源、发起自然语言查数、列出/触发/查询工作流执行情况。其中 workflow_run 是真实副作用——它会让系统真的执行动作(可能写库、发通知)。所以 MCP 的工具权限必须谨慎授予。
Q132WorkBuddy 是什么?
面向「本地 AI 客户端」的技能包/接入层,在 products/aip/workbuddy/。项目里 WorkBuddy 有两条独立接入线:独立技能包(skills/zy-action-platform,给本地客户端装的技能定义)与 connector/MCP(本项目的 MCP 服务),两者相互独立,对外引导以 config.yaml 为主。
Q133AIP 的 automation 模块是干什么的?
它是「LLM 可调用的接口目录 + Copilot」。automation/catalog/{foundry,apollo,gotham,aip}.go 把各产品的 REST 接口注册成 LLM 可调用的工具,经 apiclient 统一调用。读类接口(GET/ReadOnly)普通用户可用,写类接口限 admin。
Q134Copilot 和 MCP 有什么区别?
Copilot 是系统内的 LLM 助手,通过 function calling 调用本系统接口目录(automation/catalog);MCP 是对外的协议,让外部 AI 客户端调用本系统能力(workbuddy/tools.go)。一个向内、一个向外,工具体系是两套。
Q135MCP 鉴权怎么做?
Bearer=<client_key>,可选白名单限制来源。client_key 在 Hub 侧校验,校验通过后经 WebSocket 转发。所以 client_key 是外部访问本系统的凭证,需要妥善保管。
9安全、权限与审计
Q136系统的权限体系分几层?
四层:① 认证(JWT,跨产品互认);② RBAC(角色权限,platform/rbac);③ RLS/CLS(行级/列级安全,cls_policies / role_cls_policies 表);④ ABAC/PEP(属性级策略,abac_policies 表)。另外 Foundry 写路径还有「属性级三分权限」。
Q137ABAC/PEP 的默认语义是什么?
默认放行——deny 必须显式配置。这是项目的硬约定(记录在记忆里):新功能不得改变默认放行语义,否则会大面积误伤已有功能。
Q138RLS 和 CLS 的区别?
RLS(Row-Level Security)限制能看到哪些行(如「只能看自己区域的订单」);CLS(Column-Level Security)限制能看到哪些列(如「看不到客户手机号」)。两者在工作流 query_data 节点和 NLQ 执行时都会自动叠加。
Q139跨产品为什么 token 会 401?
因为 SECRET_KEY 不一致。若某个产品启动时 cwd 不对(cd action/bin 而不是 cd action),会读不到 config.yaml,SECRET_KEY 退化为仓库根 .env 首条(33 字符 vs 79 字符),签发的 JWT 其他产品验不过 → 401 + 前端被登出。启动 cwd 必须是 action/。
Q140SECRET_KEY 太弱会怎样?
会导致配置归零、Token 立即过期。项目在 config 处做了 fallback 补默认值。同样,SECRET_KEY 弱也会影响 AES-GCM 加密的强度(masterKey 由它派生)。
Q141审计日志记录在哪?
audit_logs 表(platform/model/models.go:361 AuditLog),另有归档表 AuditArchive(:382)。查询接口 GET /api/v1/audit/logs。Foundry 写路径的 stepAudit 不可跳过。
Q142国密算法在哪用?
SM3 / SM2(platform/security),用于信创合规场景(见 wiki/XINCHUANG.md)。这与 AES-GCM 并存,按部署环境选用。
Q143webhook 为什么禁用 execute_foundry_action?
因为 webhook 是外部可触发的(只需 secret)。若允许它直接执行 Foundry 动作,等于把「改数据」的能力暴露给外部触发方。这是最小权限的体现。
Q144为什么禁止 call_api 引用 trigger 模板?
防止 SSRF 与注入:如果 URL 里可以拼外部输入(trigger 数据),攻击者就能构造请求打到内网服务或注入恶意参数。禁止引用就从源头切断了这类攻击面。
Q145MCP 的 workflow_run 有安全风险吗?
有——它是真实副作用。所以 MCP 的 client_key 必须严格保管,白名单要收紧。service_token 同理:配了它就等于用 admin 身份检索,对象级过滤失效。
Q146数据源凭证泄漏的风险点在哪?
两处:① 若 TryDecryptSecret 兼容明文,存量明文密码仍是风险,应尽快全量加密;② 响应脱敏若被绕过(如某些调试接口),会回显密文或明文。代码约定是「响应侧一律 convertModelToSchema 脱敏,勿直接回显本结构」。
Q147「对象级权限」和「记录级 RLS」什么区别?
对象级权限管的是「能不能访问 Order 这个对象类型」(本体层面);RLS 管的是「在 Order 的哪些行上能操作」(记录层面)。两者叠加生效。
Q148怎么防止工作流被无限触发?
cron 有最小间隔约束;事件触发有游标(workflow_event_cursor)保证不重复消费;写路径有幂等登记防重复执行。但没有全局的限流/熔断——这是当前的一个边界。
Q149SQL 注入怎么防?
三道:① Text2SQL 是 LLM 生成,但执行前经连接器 TransformSQL 规范化;② call_api 禁止引用 trigger 模板;③ 写路径用参数化 ExecWrite(ctx, sqlStr, args...)(args 是参数)。注意 query_data 节点的 SQL 模板渲染需要警惕拼接。
Q150系统的安全「默认拒绝」还是「默认放行」?
都有,分场景:ABAC/PEP 默认放行(deny 显式);Connector 类型默认拒绝(白名单,需显式 AllowAdvancedTypes);跨产品写接口默认限 admin(automation/catalog)。理解这三个默认值是安全评审的关键。
10五产品矩阵与协作
Q151LightAIP 的职责边界是什么?
AIP 负责智能分析与编排:NLQ 智能查数、工作流、Automation Copilot、知识库/RAG、决策证据链、LLM 网关与评测、Agent 编排。它不拥有语义层——本体的权威定义在 Foundry,AIP 只消费。
Q152LightFoundry 的职责边界是什么?
Foundry 负责数据集成与本体:数据源、管道/同步、数据集、本体(对象/属性/链接/动作)、函数、SQL 工作台、Notebook、仪表盘、报表、质量、流事件、实体消解、Marketplace、MLOps、低代码 Apps、打标、治理、血缘,以及最关键的动作写路径。
Q153LightApollo 做什么?
供应链规划与交付运维(对标 Palantir Apollo)。端口 18082,前端用 /apollo-api 前缀。
Q154LightGotham 做什么?
情报分析(对标 Palantir Gotham):多源融合、实体消解、知识图谱、时空分析。端口 18083,前端用 /gotham-api 前缀。已有完整 PRD/TAD 文档。默认图存储为 disk JSON 落盘(config.yaml:51-54)。
Q155LightSwift 做什么?
Swift 数据导入流水线,面向金融结算场景(SWIFT + 卫星通信、星上结算、国密通信)。先仿真:GAC 网关与合规名单均为仿真实现。
Q156五产品之间怎么通信?
三层:① 共享平台底座——同一个 Go module zy_action,都 import zy_action/platform/*;② HTTP 互调——apiclient(products/aip/automation/apiclient/client.go)+ AIP 启动时装配的 apiClients(server.go:505-570);③ 共享数据库(同一 PG 库)+ 共享 JWT(secret_key 必须一致)。
Q157为什么五个产品要共享数据库?有什么好处和代价?
好处:跨产品查询简单(用户、对象、上下文都现成),无需数据同步,MVP 开发快。
代价:模块耦合、表名冲突风险(见 Q44 同名表问题)、单点故障。这是「单机优先、不过度封装」指导思想的必然结果,代价换来的是开发与运维的简单性。
Q158「共享数据库」下最容易出什么问题?
同名表冲突。已有实例:ontology_object_types / ontology_properties 在 platform/model/ontology.go(AIP)和 products/foundry/ontology/models.go(Foundry)里各定义一次,列集互斥、ID 不共享。共库时可能互相干扰。防御措施见 Q47。
Q159跨产品调用怎么鉴权?
JWT 透传 + security.username_fallback: true(config.yaml:22-30):AIP 用户与 Foundry 同名用户自动对齐,无需额外 token。前提是各产品的 secret_key 一致。
Q160如果要在 AIP 里查 Apollo 的数据,怎么做?
通过 call_api 节点或 apiclient 调 Apollo 的 REST 接口(本读本 Q108)。不存在「跨产品直连数据库查询」的推荐做法——虽然共享库让技术上可行,但那会绕过 Apollo 的权限与业务逻辑。
Q161五产品的产品故事(story)文档在哪?
docs/story/{aip,foundry,apollo,gotham,swift,cross}/,以及 wiki/website/story/。这些是按用户旅程讲的故事站,适合给客户演示时理解场景。
Q162跨产品有哪些演示场景?
docs/story/cross/ 是跨产品场景。docs/ai-action-demo-gotham.html 是 Gotham 专项演示。docs/ai-action-demo.html 是端到端最小闭环(库存补货剧情),docs/ai-action-demo-full.html 是全功能纵深版。
Q163前端文档在哪?
docs/frontend-intro-v5/markdown/(含各产品目录),HTML 配套在 docs/frontend-intro-v5/html/。信息基准是 v5 Stage 6(2026-08-30)。
Q164后端模块说明文档在哪?
wiki/docs/<产品>/*.md 与 wiki/docs/<产品>/API/*.md。修改代码后需要同步更新 API 文档、模块说明和前端 UI 介绍。
Q165产品 PRD / TAD 在哪?
action/wiki/**/PRD-v4.md 与 action/wiki/**/TAD-v4/,v5 升级在 action/wiki/upgrade-v5/。当前已到 v5 版本。
11实现程度与边界
这一章非常重要:分清「已实现」「有条件」「仿真」,是与客户/领导沟通时的关键。下面按模块给判定,判定以 2026-09 当前分支代码核实为准。
Q166怎么判断一个功能是「真」还是「演示」?
三个检查点:① 后端有没有真实路由注册(看 server/server.go);② 有没有端到端闭环(不只是返回假数据);③ 有没有真实副作用(写库/发消息/调外部)。三者齐备才算「可演示」。
Q167「可演示 / 部分实现 / 仿真」三档的标准?
| 判定 | 标准 |
|---|---|
| 可演示 | 后端路由核实存在 + 端到端闭环跑通 |
| 部分实现 | 真实实现,但有明确功能边界或降级路径 |
| 仿真 | 工程链路真实,但业务域数据是模拟的 |
Q168明确的「仿真」模块有哪些?
① Swift 的 GAC 网关(products/swift/gac/client.go:4-140);② Swift 合规名单 mock(hcc/compliance.go:41);③ Gotham 默认 disk JSON 落盘(config.yaml:51-54)。
Q169明确的「有条件」模块有哪些?
① 飞书/邮件通知——未配凭证即失败;② OAG——默认关闭且需 column_map;③ 工作流事件触发——需要 Foundry 可达;④ 动作写路径——需要数据源可写、权限足够。
Q170AIP 的 demo 数据是怎么来的?
由 bootstrap 启动时重建 demo SQLite(products/aip/server/bootstrap.go:65 指向 ./data/aip_demo_datasource.db),包含 orders / customers / products 三张表。元数据库在 data/platform.db(:68)。
Q171「降级实现」指什么?
指某些模块在缺少外部依赖时用简化实现顶上。AGENTS.md 提到已补齐的降级实现包括:RAG 向量库、深度/ESG 报告流水线、bootstrap 初始化。这类实现能满足功能,但不是最优方案。
Q172有什么已知的「隐患」需要关注?
① 共库同名表冲突(Q44/Q158);② OAG 配置热重载不生效(Q72);③ service_token 绕过对象级权限(Q73);④ 无全局限流熔断(Q148);⑤ 前端 API client 混用会 404(Q16)。这些建议记录到 wiki/todo.md。
Q173系统最大的技术债是什么?
从代码看,是本体层的双实现(AIP MVP 本体 vs Foundry 完整本体,同库不同定义)。它既是 MVP 快速验证的产物,也是长期维护的负担。solution 是 aip_ 前缀隔离(已在规划)。
Q174为什么说「底座共享、本体各建」?
因为数据源注册(data_sources 表)和连接器(platform/connector)是全平台共享的;但本体模型两产品各建一套(platform/model/ontology.go vs products/foundry/ontology/models.go)。这是本系统最需要理解的一个架构特征。
Q175如果让我接手,最先该做什么?
① 按 action/wiki/dev_progress.md 读进度;② 读 action/wiki/dev_memory/ 下的经验文档(特别是 db_shared_analysis.md);③ 起五个产品确认 health 200;④ 跑一遍 docs/ai-action-demo.md 的最小闭环;⑤ 再动代码。
12高频误解澄清
Q176误解一:AIP 数据源和 Foundry 数据源是两套独立系统。
纠正:是同一张 data_sources 表、同一个 platform/model.DataSource 模型、同一套连接器。两个产品只是不同的前端视图与命名习惯。所以你在 AIP 里配的数据源,Foundry 侧可能(按名字)用得上——这也是 OAG 的 data_source_map 存在的原因。
Q177误解二:OAG 只是配置项,没真正接线。
纠正:OAG 链路完整接通——配置(config.go:84-109)→ 装配(server.go:359)→ 检索(oag/client.go:202-239)→ 裁剪(oag/service.go)→ 注入(text2sql.go:148-168)。只是默认关闭,而本仓库的 config.yaml 已开启为 true。真实生效。
Q178误解三:系统只是演示,不真的写外部库。
纠正:writepath/pipeline.go:402 真的执行 conn.ExecWrite,platform/connector/postgresql.go:249-259 真的 db.Exec。写外部库是真实的(Swift/Gotham 等业务域才是仿真)。
Q179误解四:AIP 可以直接查 Foundry 本体数据(像查数据源一样)。
纠正:不存在「ontology-backed datasource」。AIP 查 Foundry 本体必须走 HTTP(OAG 语义检索或 /objects/:id/query),不能把本体当数据源直连。
Q180误解五:工作流和 NLQ 是两套独立的查询能力。
纠正:工作流的 nlq_query 节点复用同一条 NLQ 链路。一次实现,两处使用。
Q181误解六:函数支持 JS/Python 脚本。
纠正:函数用的是自研表达式解释器(expression.go 手写 tokenizer + Pratt parser),不是 JS/Python/WASM。支持 {{var}}、算术、逻辑、函数调用与递归(限深)。
Q182误解七:系统用了消息队列做事件驱动。
纠正:没有外部 MQ。事件走数据库轮询 + 内部 bus(platform/cdc)+ 游标持久化(workflow_event_cursor)。Foundry 对象变更事件是 AIP 每 30s 拉取的。
Q183误解八:五个产品是微服务,可以随意拆分部署。
纠正:是共享 database + 共享 module 的独立进程,不是微服务。它们耦合度远高于微服务(同库、同模型、同底座),拆开部署要小心表冲突与 JWT 一致性。
Q184误解九:前端所有页面用同一个 API client。
纠正:按产品分流——Apollo 用 apolloClient.js、Gotham 用 gothamClient.js、其他用 client.js。混用会 404。
Q185误解十:ABAC 是新加的严格管控。
纠正:ABAC/PEP 的默认语义是放行,deny 需显式配置。它不是「默认严格」,而是「默认宽松 + 显式收紧」。新功能不得改变这个语义。
Q186误解十一:LLM 生成的 SQL 会直接执行,很危险。
纠正:执行前有连接器 TransformSQL 规范化 + RLS/CLS 权限裁剪。而且写操作走的是 Foundry 的受控 write-path(有多重校验),不是让 LLM 直接生成 UPDATE/DELETE 执行。
Q187误解十二:改 config.yaml 就能热更新所有行为。
纠正:OAG 的 base_url 与 enabled 在构造期冻结,热重载不生效,必须重启。其他配置项的生效情况需逐个确认。
Q188误解十三:MCP 是系统内的 Copilot 接口。
纠正:MCP 是对外协议(products/server Hub + workbuddy/tools.go),供外部 AI 客户端调用;系统内的 Copilot 走的是 automation/catalog 的 function calling。两套工具体系。
Q189误解十四:写路径可以随便重试,不会重复写。
纠正:靠幂等登记(idempotency.go 的 Begin/Finish)保证重试不重复写,而不是「天然安全」。如果幂等登记本身失败,仍有重复风险。
Q190误解十五:这个项目和 Python 版 ChatBI 是同一套。
纠正:src/ 是 Python 遗留代码(FastAPI),只读参考,不再新增开发。src_go/ 是 Python 后端的 Go 迁移主体(51 模块已迁移验证)。action/ 是新产品方向(对标 Palantir 矩阵)。三者是不同阶段的产物,不要混淆。
13部署与运维
Q191本地开发的构建命令是什么?
cd action && go build ./...、go test ./...、go vet、gofmt。前端构建:cd action/web && npm run build。npm 路径 F:\Program Files\nodejs\npm.cmd。
Q192为什么 bash 里每条命令都要带 cd 前缀?
因为 bash 的 cwd 每次重置。项目约定:所有命令带 cd /f/chatBI/chatBI_dev && ... 前缀(该项目的历史路径),git 用 git -C ... ...。
Q193怎么停止进程?
必须用 PowerShell:/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe -NoProfile -Command "Stop-Process -Name 'xxx' -Force"。bash 的 taskkill 经 WOW64 重定向到 32 位,无法杀死 64 位进程。
Q194端口被占用了怎么办?
换端口。AIP 默认 18080(8080 被系统 httpd.exe 占用、8000 被其他进程占用,勿再用)。改端口后记得同步修改前端代理与跨产品 base_url 配置。
Q195Windows 下 .bat 脚本有什么坑?
必须 CRLF 换行 + GBK(ANSI) 编码写中文,不要用 UTF-8 + chcp 65001(cmd 解析会乱码)。另外 cygwin 的 sed 改 bat 会把 \r 剥成 LF,破坏 CRLF。
Q196本地测试用 Docker 吗?
不用。这是 cygwin 环境(兼容 Linux 命令),进程检测用 tasklist,停止进程用 PowerShell。
Q197测试节奏有什么约定?
攒到批次末尾统一跑。开发中改几处就反复跑 go test 太慢(有的测试要几分钟);除非单测很快(秒级)且能立即验证本次改动,否则不要中途频繁跑。另外:已跑通/已验证的步骤不重复跑,中断后按其受影响范围续跑。
Q198临时文件和脚本放哪?
脚本放 action/scripts/、临时文件放 action/temp/、常规测试放 action/test/。复杂或带引号的命令先写入 temp/xxx.sh 再执行(引号在 Windows bash 下易出错)。
Q199提交代码有什么约定?
① 严禁 git add .,只显式 add 当前改动与新增文件;② 注释/提交信息/文档全部中文;③ Conventional Commits(feat: / fix: 等),末尾附 Co-Authored-By: Qian Shou / zytech <pyq@zyinfo.pro>;④ action/release/ZY-Action-Platform_v*/ 路径下不提交。
Q200改完代码要更新哪些文档?
三类:① API 文档(wiki/docs/<产品>/API/);② 模块说明(wiki/docs/<产品>/*.md);③ 前端 UI 介绍(docs/frontend-intro-v5/markdown/)。若要求更新版本日志,同步 action/release/ZY-Action-Platform_v2026.最新的/CHANGELOG.md 与 html。故事开发结束后写入 wiki/changelog/date-n-name.md。
Q201遇到问题先查哪里?
顺序:① action/wiki/dev_memory/(经验文档);② action/wiki/dev_info.md(常见问题提炼);③ action/wiki/todo.md(已知隐患);④ 搜索 5 次以上仍未解决可上网搜。
Q202发现新隐患怎么处理?
记录到 action/wiki/todo.md,若无其他任务则提醒用户,批准后修复。这保证了隐患有归属,不会被遗忘。
14证据索引
以下把本读本引用的关键源码位置集中列出,方便你逐条核对。行号以 action-v5 分支编写时为准,重构后请按符号名检索。
数据源与连接器
| 主题 | 位置 |
|---|---|
| DataSource 模型(data_sources 表) | platform/model/models.go:19-26 |
| TableSchema / ColumnSchema | platform/model/models.go:30-60 |
| 连接器实现 | platform/connector/{mysql,postgresql,sqlite}.go |
| 连接器缓存服务 | platform/datasource(ConnectorCacheService) |
| 真实写执行 | platform/connector/postgresql.go:249-259 |
本体(Ontology)
| 主题 | 位置 |
|---|---|
| Foundry 本体模型 | products/foundry/ontology/models.go:90-111(ObjectType)、:140-163(Property) |
| AIP 本体模型 | platform/model/ontology.go:29(object_types)、:54(properties)、:86(link_types) |
| 本体查询翻译 | products/foundry/query/translate.go:148-149 |
| 语义检索服务 | products/foundry/semantic/search.go:122-125,298 |
| 表达式解释器 | products/foundry/workflow/expression.go:1-20,340-580 |
| 函数服务 | products/foundry/ontology/function.go:197-220,227-251 |
| 共库分析文档 | wiki/dev_memory/db_shared_analysis.md(§1.3 行 141-153、结论表 419) |
OAG
| 主题 | 位置 |
|---|---|
| 配置结构体与默认值 | platform/config/config.go:84-109、:1120-1131、:554-559 |
| 本仓库实际配置 | config.yaml:74-85 |
| 装配 | products/aip/server/server.go:359,365-380,1357-1359 |
| 检索客户端 | products/aip/oag/client.go:1-7,131-137,202-239,250 |
| 上下文组装 | products/aip/oag/service.go:60,114-165,206,266,358,425,447 |
| 降级路(本地本体) | products/aip/ontology/service.go:386-494 |
| 注入优先级 | products/aip/nlq/orchestrator.go:216,241-310,259-263,274-281 |
| Prompt 渲染 | products/aip/nlq/text2sql.go:113-186,148-168、prompt/templates.go:30-50 |
| 动作代理端点 | products/aip/oag/oag_handlers.go:28-30 |
NLQ 与执行
| 主题 | 位置 |
|---|---|
| 查询执行器 | products/aip/nlq/executor.go:70-115 |
| 查询日志模型 | platform/model/nlq_query_log.go |
工作流
| 主题 | 位置 |
|---|---|
| 触发配置 | products/aip/workflow/definition.go:170-201 |
| DAG 引擎 | products/aip/workflow/engine.go:112 |
| 节点注册表 | products/aip/workflow/nodes.go:69-91 |
| 节点类型常量 | products/aip/workflow/nodetypes.go:22-108 |
| 通知节点 | products/aip/workflow/nodes.go:363-432 |
| 调外部 API 节点 | products/aip/workflow/nodes.go:468-514 |
| 执行 Foundry 动作节点 | products/aip/workflow/nodes.go:544-619 |
| 事件调度器 | products/aip/workflow/event.go:31-227,230-246 |
| cron 触发 | products/aip/workflow/cron.go:108-160 |
| webhook 入口与发布校验 | products/aip/workflow/handler.go:60、service.go:282-296,342,346 |
| CDC 检测与总线 | platform/cdc/detector.go、platform/cdc/bus.go:63 |
写路径(写回外部系统)
| 主题 | 位置 |
|---|---|
| 写路径执行器 | products/foundry/writepath/pipeline.go:154,402,414-443,1286-1342 |
| 幂等登记 | products/foundry/writepath/idempotency.go |
MCP / 本地 AI 接入
| 主题 | 位置 |
|---|---|
| MCP Hub | products/server/mcp.go:96-198、products/server/README.md |
| MCP 工具集 | products/aip/workbuddy/tools.go:5-11,35-91、workbuddy/server.go:118,120 |
| 自动化接口目录 | products/aip/automation/catalog/{foundry,apollo,gotham,aip}.go、catalog/foundry.go:98-118 |
| Copilot 工具 | products/aip/automation/tools.go:196,649 |
| 跨产品客户端 | products/aip/automation/apiclient/client.go、products/aip/server/server.go:505-570 |
安全与审计
| 主题 | 位置 |
|---|---|
| 加密与秘密管理 | platform/security(EncryptSecret / TryDecryptSecret) |
| CLS / ABAC 策略模型 | platform/model/models.go:198,209,232 |
| 审计模型 | platform/model/models.go:361,382 |
| 跨产品鉴权配置 | config.yaml:22-30 |
| Connector 白名单 | platform/connector(AllowAdvancedTypes) |
仿真域
| 主题 | 位置 |
|---|---|
| Swift 仿真网关 | products/swift/gac/client.go:4-140 |
| Swift 合规名单 mock | products/swift/hcc/compliance.go:41 |
| Gotham 图存储配置 | config.yaml:51-54 |
文档地图
| 主题 | 位置 |
|---|---|
| OAG 完整设计 | wiki/docs/aip/oag.md、wiki/docs/aip/API/oag-API.md |
| 本体集成 | wiki/docs/aip/ontology_integration.md |
| 设计总览 | wiki/design/design_overview.md:133,283-285、design_aip.md:67 |
| 开发进度 | wiki/dev_progress.md |
| 经验记忆 | wiki/dev_memory/*.md |
| 隐患清单 | wiki/todo.md |
| 常见问题提炼 | wiki/dev_info.md |
| 路线图与产品矩阵 | wiki/ROADMAP.md、wiki/OVERVIEW.md |
| 信创 | wiki/XINCHUANG.md |
| 前端介绍(v5) | docs/frontend-intro-v5/markdown/ |
| 产品故事 | docs/story/{aip,foundry,apollo,gotham,swift,cross}/ |
| 演示手册 | docs/ai-action-demo.md(最小闭环)、docs/ai-action-demo-full.md(全功能) |
| PRD / TAD | wiki/**/PRD-v4.md、wiki/**/TAD-v4/、wiki/upgrade-v5/ |
15深度链路 A:一次动作执行的完整旅程
这一章把「行动系统」最重要的一条链路——从用户/AI 下决定到外部数据库被真实改写——拆成 12 个可观测的步骤。每个步骤都标出「看哪段代码」和「失败会怎样」,方便你排障。
Q203为什么「审计」步骤不可跳过?
因为系统的核心承诺是「行动可追溯」。如果审计可以跳过,就等于允许「悄悄改数据」,整个安全模型崩塌。所以 stepAudit 是流水线的强制环节,而不是事后补记。
Q204步骤 2/3/4 三道权限为什么要分开做?
它们回答三个不同的问题:RBAC 回答「你有没有资格执行 expedite 这个动作」;RLS 回答「这个动作能作用在哪些记录上」;属性权限回答「这次修改能不能碰这些字段」。混在一起会导致「有动作权限的人能改任意记录任意字段」的越权。分开做才严密。
Q205步骤 5 乐观锁为什么返回 409 而不是自动重试?
因为自动重试可能覆盖别人的业务意图。409 把决定权交回调用方:它能判断是「我重读最新值再改」还是「放弃并提示用户」。这是「宁可报冲突,不静默覆盖」的保守选择。
Q206步骤 6 幂等登记为什么必须在执行前?
因为要防的是「执行成功但响应丢失 → 调用方重试 → 重复执行」。必须在执行前登记意图(Begin),执行后标记完成(Finish)。如果登记在执行后,重试窗口内就会漏防。
Q207步骤 9 的事件有什么字段?下游怎么用?
事件记录「哪个对象、什么变更、何时、由谁」。AIP 的 EventScheduler 用游标(workflow_event_cursor)保证不漏不重,按工作流的 trigger_config.filters 匹配后触发。下游典型用途:库存变更 → 触发补货工作流;订单状态变更 → 触发通知工作流。
Q208这条链路里哪一步最可能失败?
实践中最常见:① 步骤 1 Schema 不匹配(本体属性改了但动作参数没同步);② 步骤 2 权限不足(新用户没授权);③ 步骤 7 数据源不可写(连接串错、库只读、网络不通);④ 步骤 5 并发冲突(高频写入场景)。排障时按这个顺序看日志。
Q209怎么「演练」一次动作而不真正写库?
用 mode=VALIDATE(只校验不执行)或 VALIDATE_AND_EXECUTE(先全量校验,通过才执行)。这是上线前的标准做法,能在不产生副作用的前提下验证配置正确性。
Q210这条链路和 Palantir 的对应关系?
对应 Palantir Foundry 的 Action + Writeback 机制:本体定义动作(Action Type)→ 编辑类型(create/modify/delete/link)→ 写回(Writeback)到源数据。本系统的 writepath 就是这个机制的工程实现,多了幂等、乐观锁和强制审计三个工程增强。
故障注入演练(排障训练)
| 现象 | 最可能原因 | 先查什么 |
|---|---|---|
| 动作执行返回 400 参数错误 | Step 1 Schema 校验失败 | 本体属性定义 vs 动作参数定义 |
| 动作执行返回 403 | Step 2/3/4 权限拒绝 | RBAC 授权、RLS 策略、属性权限 |
| 动作执行返回 409 | Step 5 乐观锁冲突 | 是否有并发写同一对象 |
| 动作「成功」但数据没变 | mode 是 VALIDATE,或 Step 7 写到别的库 | 动作 mode 配置、数据源连接串 |
| 同一动作被执行两次 | Step 6 幂等登记异常 | idempotency 记录、Begin/Finish 日志 |
| 下游工作流没被触发 | Step 9 事件未生成,或 scheduler 游标卡住 | object_change_events、workflow_event_cursor |
16深度链路 B:一次提问的完整旅程
这一章拆解「用户在 AIP 里问一句自然语言」到「看到答案」的全过程,重点在 OAG 与权限的介入点。
Q211为什么 OAG 要在 Text2SQL 之前介入,而不是之后?
因为它的作用是影响 SQL 的生成(告诉 LLM 业务口径、对象关系、可用指标),而不是解释结果。放在后面就失去了意义。这是「上下文工程」的典型位置——注入点必须在生成决策之前。
Q212为什么要把物理 schema 和 OAG 语义「并联」注入?
因为两者互补:物理 schema 告诉 LLM 有哪些表/列可以写(语法正确性),OAG 语义告诉 LLM 这些表列的业务含义和口径(业务正确性)。只给 schema 会写出语法对但口径错的 SQL;只给语义会写出业务对但列名错的 SQL。两者一起才完整。
Q213为什么 prompt 里要有「OAG 只是口径参考」这个约束?
防止 LLM 把本体语义描述当成表结构照抄进 SQL。本体里有 base_table、object_type_id 这类元数据字段,如果 LLM 误把它们当作可查询列,就会生成查不存在的列的错误 SQL。这条约束是踩过坑之后的加固。
Q214用户 token 在跨产品调用时怎么传递?
通过 OAG client 的 HTTP 头透传(client.go:131-137),除非配置了 service_token(那时优先用服务账号,会绕过对象级权限)。配合 username_fallback 的同名用户对齐,权限在 Foundry 侧按同名用户生效。
Q215如果 OAG 服务挂了,用户会感知到吗?
基本不会。OAG 失败只 warn 并降级到本地本体/RAG(orchestrator.go:248-273),链路继续。代价是 SQL 质量可能下降(少了口径公式),但在简单查询上几乎无感。这是「降级不阻断」的设计价值。
Q216RLS/CLS 在哪个环节介入?会不会被 LLM 绕过?
在SQL 执行环节(executor.go)叠加,不在 SQL 生成环节。这是关键设计:LLM 生成的 SQL 再怎么变,执行前都会被权限层改写(加上行过滤条件、去掉无权限的列)。所以LLM 无法绕过权限——它只能决定查什么,不能决定能查到什么。
Q217「列级安全被绕过」的典型误区是什么?
误以为「不让 LLM 生成某个列就是列级安全」。实际上必须靠执行层裁剪:即使 LLM 生成了 SELECT phone FROM customers,CLS 也会把它变成 SELECT NULL FROM customers 或直接拒绝。安全必须在下游执行层保证,不能依赖上游生成。
Q218排查「查出来的数不对」该怎么定位?
按链路倒查:① 看 nlq_query_log 里保存的实际 SQL——如果 SQL 本身写错了,问题在生成环节(查 OAG 是否命中、schema 是否过期);② 如果 SQL 对了但结果不对,问题在执行环节(RLS/CLS 是否过度裁剪、数据源是否连到正确的库);③ 如果两者都对,问题可能在数据源本身(数据没同步)。
Q219为什么「AIP 数据源名」还要映射到「Foundry 数据源名」?
因为 OAG 的语义检索是在Foundry 侧按数据源过滤的,而用户提问时意图识别给出的是 AIP 侧的名字。若不做映射,Foundry 侧找不到对应数据源,OAG 就检索不到东西,静默降级。这是「看起来配了但没效果」的经典原因。
Q220这条链路里「成本」在哪?
三处:① LLM 调用(Text2SQL 生成 + 结果解释);② embedding 与向量检索(RAG);③ OAG 的 HTTP 往返(3s 超时)。本仓库把 OAG 的 max_tokens 放大到 20000,意味着 prompt 更长、LLM 成本更高——这是「用成本换准确率」的显式选择。
Q221怎么衡量这条链路「做得好不好」?
指标:① SQL 一次生成通过率(不报错);② 结果业务正确率(人工抽检);③ 端到端延迟;④ OAG 命中率;⑤ 权限拒绝率(异常高说明配置有问题)。项目有 evaluation 模块(products/aip/evaluation)与 monitoring 模块支撑。
Q222如果我要给团队讲这个系统,讲哪三张图?
① 全景图(本读本 Q10):数据源→数据集→本体→NLQ→工作流→写回→事件;② 动作执行流水线(第 15 章):九道关卡从校验到审计到事件;③ NLQ 三段式降级(第 16 章):OAG → 本地本体 → RAG 与权限执行层的关系。三张图讲完,系统就讲清楚了。
17八个实战场景
这一章用八个具体场景「跑一遍」系统,每个场景给出:要动哪些模块、关键配置、常见坑。这些场景覆盖了系统的主要能力面,也适合作为演示脚本。
场景一:接入一个客户业务库并让 AIP 能查
POST /api/v1/datasources注册数据源(类型选 POSTGRESQL/MYSQL,填连接串)。密码自动 AES-GCM 加密落库。POST /api/v1/datasources/:id/test验证连通性。POST /api/v1/datasources/:id/import-metadata导入表/列元数据(注意:导入前会清理旧元数据,避免残留表名误导 RAG)。- 给用户开查询权限(RBAC),配置必要的 RLS/CLS 策略。
- 在 AIP 提问验证。若走 OAG,记得把数据源名加进
foundry.oag.data_source_map。
坑:SQLite 需先 ensureDir;连接串里库名写错会 test 通过但 import 失败;权限没配会返回空结果而不是报错。
场景二:把一张物理表变成可查询的本体对象
- 在 Foundry 定义 ObjectType:
DataSourceID指向数据源,BaseTable指向物理表。 - 定义 Properties:每个属性用
MappedColumn映射到物理列,data_type与列类型对齐。 - 定义 LinkTypes 描述对象间关系(如 Order → Customer)。
- (可选)定义 Metric 带口径公式,供 OAG 注入。
- 用
POST /api/v1/objects/:id/query验证实例查询能通。
坑:data_type 与物理列类型不一致会导致查询翻译失败;列的改动记得同步更新本体映射。
场景三:让「数据一变更就自动处理」
- 在工作流里加
trigger节点,type 设为event,配置filters(匹配哪些对象、哪些变更类型)。 - 配置后续节点:
query_data取详情 →ai_analysis生成判断 →condition分支 →execute_foundry_action或send_notification。 - 发布工作流(草稿态不生效)。
- 让 Foundry 侧产生一次对象变更,观察 AIP 的
EventScheduler在 30s 内拉取并触发。
坑:忘了发布;filters 写太严匹配不到;Foundry 不可达时事件拉取失败(看日志);游标卡住会导致事件堆积。
场景四:外部系统主动推数据进来触发处理
- 工作流 trigger type 设为
webhook,系统分配一个/webhooks/:name路径。 - 外部系统带
X-Webhook-Secret调用该路径。 - 工作流按 payload 处理(注意:发布期已强制禁用
execute_foundry_action与 trigger 模板引用)。
坑:这个工作流不能改数据(设计如此,防外部越权);secret 泄漏等于任何人都能触发,要定期轮换。
场景五:用 AI Copilot 创建并运行一个自动化
- 在 AIP 的 Automation 里对话,让 Copilot 用
create_automation创建工作流。 - Copilot 调用
run_automation_now立即触发验证。 - 查看执行记录确认结果。
坑:Copilot 能调的接口是 automation/catalog 里的白名单;写类接口限 admin,普通用户问「帮我改数据」会被拒。
场景六:给外部 AI 客户端开 MCP 接入
- 部署
products/server作为 Hub(独立于本地产品包)。 - 本地 AIP 实例通过
/action/ws出站注册到 Hub。 - 外部 AI 客户端用
GET /action/mcp建 SSE,用POST /action/mcp发 JSON-RPC,Bearer=client_key鉴权。 - 客户端可调六个工具(列数据源、NLQ 查数、列/触发/查工作流)。
坑:workflow_run 是真实副作用,client_key 必须严格保管、白名单收紧;Hub 只是中继,业务在后端进程内。
场景七:配一次外部接口调用(调客户 ERP)
- 在工作流里加
call_api节点,填目标 URL、方法、请求体模板。 - 设定超时(默认 15s)与响应上限(1MiB)。
- 注意失败非致命——需要「失败也要处理」的话,用
condition检查节点输出。
坑:没有连接器抽象,凭证需自行处理(放 header);URL 禁止引用 trigger 模板(防 SSRF);响应超 1MiB 会被截断。
场景八:排查「查询结果不对」
- 查
nlq_query_log拿实际执行的 SQL。 - SQL 错 → 查上下文:OAG 是否命中(看日志)、元数据是否过期、
column_map是否缺失。 - SQL 对但结果少 → 查 RLS/CLS 是否过度裁剪。
- SQL 对但结果空 → 查数据源是否连到正确的库(
FOUNDRY_DEMO_DB_PATH类环境变量是否同步)。 - 都正常 → 查数据同步链路(数据集是否刷新)。
坑:测试隔离时环境变量分库,写路径可能连到旧的演示库;这是「本机看着对、测试环境不对」的经典原因。
场景九(附加):本机起一套完整环境
- 确认 PostgreSQL 可用(
F:\PostgreSQL\17\bin),建库chatbi_action_dev。 - 在
action/目录下按顺序起五个产品:./bin/foundry.exe、./bin/aip.exe(cwd 必须是action/)。 - 逐个
curl --noproxy '*' .../health确认 200。 - 起前端:
cd action/web && npm run dev(Vite 5173,代理按前缀分流)。 - 注册/登录(
POST /api/v1/auth/register|login),跑一遍docs/ai-action-demo.md的闭环。
坑:cwd 不对导致 SECRET_KEY 不一致(跨产品 401);端口被占用(8080/8000 勿用);前端 API client 混用导致 404。
18深度专题 A:数据源映射表在哪配、前端有没有配置入口
先给结论(很多人会踩这个坑):data_source_map 与 column_map 只有 config.yaml 一个配置入口,没有任何前端 UI。改了必须重启进程(构造期冻结,热重载不生效)。
Q223这两个映射表到底配在哪?
配在 action/config.yaml 的 foundry.oag 段下(config.yaml:82-85):
foundry:
oag:
enabled: true
data_source_map: # AIP 数据源名 → Foundry 数据源名
aip_demo_warehouse: "foundry_demo_warehouse"
column_map: # Foundry 列名 → AIP 执行表列名
amount: "sales_amount"
Q224前端 UI 有配置的地方吗?
没有。全仓库检索 data_source_map / column_map 的结果只有:config.yaml(配置值)、platform/config/config.go(结构体与默认值)、products/aip/oag/service.go(读取使用)、products/aip/server/server.go(装配)。没有任何 .vue 或 .js 引用,即前端无法配置、也无法查看这两个映射。
Q225配置结构体是怎么定义的?
// platform/config/config.go:104-108
// DataSourceMap AIP 数据源名 → Foundry 数据源名(filters.data_source 翻译)
DataSourceMap map[string]string `mapstructure:"data_source_map"`
// ColumnMap 列名重写映射(Foundry 列名 → AIP 执行表列名,如 amount → sales_amount)
ColumnMap map[string]string `mapstructure:"column_map"`
默认值都是空 map(config.go:1130-1131),即「不配置 = 不翻译」。
Q226两个映射分别在哪一步生效?
都在 OAG 服务内部(products/aip/oag/service.go):
data_source_map:在构造 filters 时翻译数据源名(service.go:172-173),把 AIP 侧名字换成 Foundry 侧名字,保证语义检索能在 Foundry 找到对应数据源。
column_map:在注入前重写(service.go:358-393 applyColumnMap),把指标口径公式/维度/属性映射列里的 Foundry 列名整词替换成 AIP 列名。
Q227有个「内建兜底映射」是什么?
products/aip/server/server.go:1341-1380 定义了 defaultOAGColumnMap(2026-09-13 加的资产层修复),当 config.yaml 没配同名列时生效。优先级:config.yaml 显式配置 > 内建兜底(:1370-1376)。如果完全没配,启动时会打一条 warn 日志:「OAG 对象路已启用但未配置 column_map 列名映射,仅使用内建兜底映射,注入口径可能引用错误列」(:1379-1381)。
Q228为什么不做成前端可配?这是个缺陷吗?
是有意的取舍。这两个映射是部署期/接入期的静态适配数据(哪个 AIP 数据源对应哪个 Foundry 数据源、口径公式里的列名怎么翻译),不是运行期业务配置。做成 UI 需要额外的 CRUD、权限、生效机制(且改了还要能热生效),成本高于收益。这与项目「单机优先、不过度封装、用现成方式直读直写」的原则一致。
Q229那如果我需要动态映射怎么办?
当前只能改 config.yaml + 重启。如果要动态化,最小改法是:把配置读取点(server.go:1370-1404 装配处)改成从数据库表读,并提供一个简单的管理 API;oag/service.go 的 DataSourceMap / ColumnMap 字段本来就是 map[string]string,替换数据源即可,改动面不大。但要注意 server.go:358 的注释——OAG 服务在构造期创建、生命周期内不重建,所以还需要处理「重建 OAG 服务」的机制。
Q230配错了会有什么现象?
| 现象 | 原因 |
|---|---|
| OAG 好像没生效(SQL 质量没提升) | data_source_map 没配或配错,Foundry 侧按名字找不到数据源,静默降级 |
| SQL 报「列不存在」 | column_map 缺失,口径公式引用了 Foundry 列名(如 amount)而实际表是 sales_amount |
| 改了配置没变化 | 没重启进程(构造期冻结) |
| 启动日志有 OAG warn | column_map 为空,只有内建兜底 |
Q231映射表属于「配置」还是「数据」?
属于部署配置。它描述的是「本部署环境里两套命名怎么对应」,换一个客户环境就要重配。这解释了为什么它不在数据库里——数据库里的数据源是「客观存在」,映射是「本环境的主观约定」。
Q232一条排查口诀。
「OAG 没效果先看 map,SQL 报错看 column_map,改完记得重启。」
19深度专题 B:共库架构、冲突分析与拆库评估
直接回答你的三个问题:
① 「五个产品都用同一个 platform.db 读写会不会冲突?」——不会,因为根本不存在共用的 platform.db。默认拓扑下 AIP 用 data/platform.db,Foundry/Apollo/Gotham/Swift 各自用 data/<产品>_platform.db,五份独立 SQLite 文件。
② 「如果分不同 db 文件,是否影响产品间信息交换(比如用户表)?」——会影响,而且是静默影响。用户表、角色、授权、数据源、审计这些「平台级共享表」正是跨产品信息交换的载体,SQLite 分库后它们全部隔离,统一登录等特性失效。
③ 「分拆开怎么调整,工作量大吗?」——看目标。若「分拆」指各自独立 SQLite(现状默认已如此),工作量几乎为零;若指「从 PG 共库拆成真正独立」,则是架构级改造(大工作量),需要重建跨产品身份与数据可见性机制。
19.1 实际拓扑:两套模式并存
| 产品 | PG 模式默认 | SQLite 默认文件 | 建库逻辑 |
|---|---|---|---|
| Foundry | 优先 PG(chatbi_action_dev) | ./data/foundry_platform.db | bootstrap.go:147-200:PG ping 失败降级 SQLite |
| Apollo | 优先 PG(注释写「与 foundry 共用」) | ./data/apollo_platform.db | bootstrap.go:53-160 同构 |
| Gotham | 优先 PG | ./data/gotham_platform.db | bootstrap.go:54-190 同构 |
| Swift | 优先 PG | data/swift_platform.db | bootstrap.go:39-70 同构 |
| AIP | 默认禁用 PG(需 AIP_ENABLE_PG=true) | data/platform.db | bootstrap.go:206-320:显式启用 + 冲突检测,否则恒走 SQLite |
五处 PG 库名常量完全一致(都是 chatbi_action_dev):foundry bootstrap.go:76、apollo :50、gotham :55、swift :39、aip :71。SQLite 路径可用环境变量 PLATFORM_DB_PATH 覆盖(五产品同名函数 platformDBPath())。
data/platform.db 这个名字看着像共享,实际只有 AIP 在用——其他产品都带产品前缀)。真正共享的只有 PG 模式的 chatbi_action_dev,而 AIP 默认还不接入。19.2 共库时到底共享了哪些表(这才是「信息交换」的载体)
| 表 | 共享内容 | 作用 |
|---|---|---|
users / roles / user_roles / role_permissions | 用户与 RBAC | 统一身份:同一用户 ID + 共享 RBAC = 一处建号全产品可用 |
auth_sessions | 会话 | 跨产品登录态 |
system_settings 的 AUTH_KEY | 授权密钥 | 统一授权:一次授权全产品激活 |
data_sources / table_schemas / column_schemas | 数据源与元数据 | 数据源注册全产品可见 |
rls_policies / cls_policies / abac_policies | 安全策略 | 策略全产品生效 |
<product>_audit_log | 审计(已按产品拆表) | 各产品独立哈希链 |
Q233统一身份是怎么实现的?为什么各产品能算出同一个 admin ID?
因为五产品的 ensureDefaultAdmin 用同一个确定性算法生成 admin 用户 ID:uuid.NewMD5(uuid.NameSpaceDNS, []byte(username))。所以四个连 PG 的进程各自 seed,算出的 admin user id 完全相同,INSERT 时靠幂等不产生重复行。这是刻意的设计——保证「独立启动、共享身份」,不是巧合。
Q234那 SQLite 分库模式下,跨产品信息交换会怎样?
全部静默隔离:每产品独立的 users/roles(各自 admin 密码可漂移)、独立的授权(AUTH_KEY 各自一份,可独立失活)、独立的数据源(互相看不见)、独立的审计链(各自完整,校验反而通过)。关键在于「静默」——系统不会报错,只是统一登录、统一授权这些特性悄悄失效。同一套代码两种拓扑行为不同,这是最容易被测试漏掉的地方(测试多用 SQLite,验证不了 PG 拓扑的问题)。
Q235PG 共库模式下有哪些真实冲突?
项目有专门的分析文档 wiki/dev_memory/db_shared_analysis.md(2026-09-05,含逐条源码证据)。已修复与仍存在的清单:
| # | 问题 | 状态 |
|---|---|---|
| 1 | api_keys:foundry/apollo 同表不同列、互相可见、撤销跨产品失效 | 已修复 apollo 改表名 apollo_api_keys(2026-09-05) |
| 2 | agent_tasks 双定义主键异构(AIP string vs Apollo uint),共表互改即互毁 | 防御已加 AIP 启用 PG 时冲突检测拒绝接入;彻底隔离仍需 aip_ 前缀 |
| 3 | ontology_object_types/ontology_properties 双定义(9 列 vs 17 列)同名互斥 | 防御已加 同上;aip_ 前缀隔离待实施 |
| 4 | 审计哈希链四进程共写必分叉 | 已根治 按产品拆表,每产品一条独立链 |
| 5 | 审计表无产品列,混表不可按产品过滤 | 已修复 五产品各落 <product>_audit_log |
| 6 | AUTH_KEY 五产品同 key upsert,授权互相覆盖 | 两面 若产品套件整体授权则正确,否则需加前缀 |
| 7 | data_sources 明文凭据全产品可读 | 已修复 改为 AES-256-GCM 加密落库(platform/crypto/aes.go) |
| 8 | AIP PG 分支被硬编码禁用、行为与文档不符 | 已修复 改为 AIP_ENABLE_PG 开关 + 冲突检测 |
| 9 | SQLite 降级下身份/授权/可见性静默隔离 | 仍存在 需明确两种拓扑的支持声明 |
| 10 | 并发 AutoMigrate DDL 锁竞争、启动风暴删 table_schemas | 仍存在 靠人工错峰启动规避 |
| 11 | 无租户列 + 共库 = 仅支持单租户单组织 | 约束 长期随 schema 方案解决 |
Q236AIP 的冲突检测具体检什么?
products/aip/server/bootstrap.go 的 checkPGSharedTableConflicts(:155-197)检两项:
① ontology_object_types 是否含 foundry 版特有列(rid / category / pk_columns 任一)——命中说明被 Foundry 17 列版占据;
② agent_tasks.id 是否为整型(bigint/integer/smallint)——命中说明被 Apollo 版占据(AIP 是 string 主键)。
任一命中即拒绝启用 PG、回退 SQLite,并打日志要求先做 aip_ 前缀隔离。这是「宁可降级也不互毁数据」的防御式设计。
Q237为什么审计表要按产品拆?
因为审计表有链式哈希(prev_hash/hash)。写入时靠进程内 sync.Mutex 保证「取上一条 → 算哈希 → 写入」的原子性。但四个 PG 进程各持独立锁实例:A 进程和 B 进程可能同时读到同一条末行,各写一条 prev_hash 相同的记录 → 链分叉,VerifyHashChain 会判为「篡改」(无法区分分叉和篡改)。拆表后每产品单写方一条链,问题根治。
Q238拆库工作量到底多大?分三种「拆」来看。
| 拆法 | 含义 | 工作量 | 代价/影响 |
|---|---|---|---|
| A. 各用独立 SQLite | 现状默认已如此(五份 <产品>_platform.db) | 几乎为零 | 统一登录/授权/数据源可见性静默失效;无需改代码,只需接受拓扑行为差异 |
| B. PG schema 隔离 | 每产品一个 schema(foundry. / apollo. …),platform 公共表进 platform. schema,users 留 platform schema 供全产品引用 | 中等 | 需适配:连接串 search_path、~14 处原生 DDL、3 处动态建表、information_schema 探测逻辑。保留统一身份,最贴合现有意图 |
| C. 库隔离 + API 互通 | 每产品独立 database,跨产品数据只经 API(符合 v5「显式 HTTP 路由通信」原则),身份改由集中认证服务签发 | 大 | 直接改变现行「五产品共库互操作」设计意图;SSO 与统一授权需重建;跨产品查询改造量大 |
Q239如果选 B(schema 隔离),具体要改什么?
- 连接串:所有产品 DSN 加
search_path=<product>,platform,或建连后SET search_path。 - 原生 DDL:约 14 处(各产品 bootstrap 里的原生 SQL)要加 schema 前缀或确保走
search_path。 - 动态建表:3 处(如
db.Table(...)形式的动态表名)需要显式 schema。 - 探测逻辑:
information_schema查询(如 AIP 的冲突检测)要限定 schema,否则会跨 schema 匹配到同名表。 - 审计拆表:
<product>_audit_log这套前缀机制可以保留(与 schema 不冲突),或改为 schema 内的统一表名。 - platform 公共表:
users/roles/data_sources/system_settings等要落到platform.schema,并按「是否全产品共享」决定归属。
因为是同一个 Go module、同一份 platform 包代码,改动是集中式的——主要在 platform/database 与各产品 bootstrap.go,不需要动业务逻辑。这是工作量「中等」而非「大」的原因。
Q240如果选 C(库隔离),为什么工作量大?
因为它触动了跨产品协作的根基:
① 统一身份没了——users 表不再共享,要新建集中认证服务(或各产品间同步用户/引 SSO);
② 统一授权没了——AUTH_KEY 单 key 模型要重做成分产品授权;
③ 跨产品数据可见性没了——数据源列表、本体、元数据在各库,要改成 API 拉取 + 缓存;
④ 确定性 admin ID 那套设计作废——需要新的账号同步或联合认证机制;
⑤ 跨产品查询(虽然不推荐直连,但共库让它可行)全部要改成 HTTP 契约;
⑥ 现有多数集成测试用 SQLite 单库,无法验证新拓扑,需要新建「多库拓扑」测试矩阵。
Q241项目文档给的「折中方案 C」是什么?
分析文档里提到的「方案 C:表前缀规范化的共库」——即不拆库,而是把缺前缀的几处补齐(主要是 agent_tasks / ontology_* 改成 aip_ 前缀),保留共库 + 统一身份的优势。文档的判断是:「若确认单机单租户是长期形态,把缺的四处前缀补齐即可冻结该架构,将投入留给产品功能。」这是当前性价比最高的路线。
Q242「现在到底该不该拆」——决策建议。
- 若目标是单机单租户演示/交付:不拆。维持现状(PG 共库 + 表前缀规范化),补完
aip_前缀隔离即可。这最小代价保留了统一身份与统一授权的产品特性。 - 若目标是对外多租户/多客户部署:考虑 方案 B(schema 隔离),改动集中在
platform/database+ 各bootstrap.go,工作量可控且保留统一身份。 - 若目标是产品完全独立发售:才考虑 方案 C(库隔离 + API),但要接受重建 SSO/统一授权的成本。
Q243不拆的话,最该做的三件事是什么?
- 补
aip_前缀隔离(agent_tasks/ontology_object_types/ontology_properties)——这是唯一残留的「定时炸弹」,完成后 AIP 才能安全启用 PG。 - settings key 加产品前缀规范(如
workflow_event_cursor→aip_workflow_event_cursor),AUTH_KEY保持全局但写明「平台授权」语义。 - 加「五产品 PG 拓扑启动冒烟」集成用例,覆盖并发 AutoMigrate、启动风暴删 table_schemas、跨产品可见性这三类共库特有问题。
Q244这份分析的原始文档在哪?可信度如何?
wiki/dev_memory/db_shared_analysis.md(2026-09-05 生成,分支 action-v5),基于 db_table_inventory.md(261 表盘点)逐条回源码验证,所有结论带 文件:行号 证据。可信度高,但注意两点时效性:
① 文档写作时 AIP 的 PG 分支还是 if false && ... 硬编码禁用(§0.1),现已改为 AIP_ENABLE_PG 开关 + 冲突检测;
② 文档 §3.3 说数据源密码「明文存储」(models.go:18),现已改为 AES-256-GCM 加密(platform/crypto/aes.go,models.go:15-18 注释为证)。
引用该文档时应以本节(19 章)的更新状态为准。
Q245用一句话总结这一章。
「不存在共用的 platform.db;SQLite 默认就是五份独立文件,PG 共库才有共享。共享的是用户/RBAC/授权/数据源这些平台表,拆开等于放弃统一身份。所以:默认拓扑不用拆,真要拆首选 schema 隔离(中等工作量、保留统一身份),彻底分库是架构级改造。」