ZY Action Platform · Reader for Mid-level Engineers

系统理解 245 问
把「行动系统」讲成一条能跑通的链路

这本读本用问答的形式,把 ZY Action Platform(对标 Palantir 的产品矩阵)从「用户点一下按钮」到「外部系统数据库真的被改写」的完整链路拆开。每一个回答都尽量给出源码位置,你可以自己打开代码核对。目标读者:接手本项目的中级工程师、需要向客户解释系统能力的 FDE、以及要做集成对接的外部开发者。

245 问 · 19 章 信息基准 2026-09-14 · 分支 action-v5 代码为准,文档为辅

0怎么用这本读本

先给结论:本系统的核心不是「一个聊天框」,而是一条从触发到写回外部系统的可审计链路。如果你只记住一句话,请记住这句:

事件/定时/人工/Agent → 工作流 DAG → 节点(查数 / NLQ / LLM 分析 / 调外部 API / 执行 Foundry 动作 / 发通知)→ 真实写回外部数据源 → 发布变更事件。

这套链路的三个关键要素是:数据源(物理连接)、本体 Ontology(业务语义层)、工作流 Workflow(执行编排)。第一次读建议按 1→7 章顺序;如果你是被某个具体问题困扰(比如「AIP 和 Foundry 的数据源到底什么关系」),直接跳到对应章节。

读本的三条约定

约定含义
源码路径所有路径相对仓库根目录下的 action/,例如 products/aip/nlq/orchestrator.go。行号以当前分支为准,代码重构后行号可能偏移,请以符号名检索。
状态标签已实现=有真实后端代码与端到端闭环;有条件=真实但受开关/凭证/边界限制;仿真=链路真实但业务域是模拟的。
冲突处理文档与代码冲突时一律以代码为准。本读本编写时逐条核对过源码,但仍可能滞后。
先纠正三个最常见的误解(详见第 12 章):① 以为「AIP 的数据源」和「Foundry 的数据源」是两套独立系统;② 以为 OAG 只是个配置开关没接线;③ 以为本系统是「演示玩具」,不真的写外部库。三条都不准确,具体看下文。

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五产品的端口分配是怎样的?

产品端口定位
AIP18080NLQ 智能查数、工作流、Copilot、知识库
Foundry18081数据集成、数据集、本体、函数、动作写路径
Apollo18082供应链规划与交付运维
Gotham18083情报分析(实体消解、知识图谱、时空分析)
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一条端到端链路长什么样?

① 数据源注册(platform/model.DataSource → data_sources 表,密码 AES-GCM 加密) ↓ ② 同步/管道拉数 → 数据集(fd_datasets,source_type=platform_ds 时带 {ds_id, query}) ↓ ③ 定义本体(ontology_object_types.DataSourceID 指向数据源 + BaseTable 指向物理表) ↓ ④ 用户提问(AIP NLQ)→ OAG 拉 Foundry 语义 → 注入 Text2SQL prompt → 生成 SQL ↓ ⑤ 连接器执行 SQL(connector.ExecuteSQL,带 RLS/CLS 行/列级裁剪) ↓ ⑥ 事件触发工作流(cron / webhook / Foundry change-event / CDC) ↓ ⑦ 工作流节点执行动作(execute_foundry_action → writepath → conn.ExecWrite 真实写回) ↓ ⑧ 发布变更事件 + 写审计日志(audit_logs)

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/databaseGORM 连接、自动迁移
platform/securityAES-GCM 加密(EncryptSecret)、JWT、国密 SM3/SM2
platform/auth + rbac认证、角色权限
platform/audit审计日志落库、归档
platform/connectorMySQL / PostgreSQL / SQLite 连接器
platform/datasource连接器缓存服务(ConnectorCacheService)
platform/cdc变更检测 + 内部事件总线
platform/scheduler统一调度器(cron 注册)
platform/llmLLM 网关统一出口

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默认值作用
enabledfalse对象路总开关
base_url""空则复用 foundry.base_url
service_token""服务账号 JWT(有 OAG_SERVICE_TOKEN 环境变量兜底)
max_tokens2000注入 token 预算
max_objects20TopN 对象数上限
max_props_per_object8每对象属性上限
max_metrics_per_object5每对象指标上限
timeout_ms3000单次语义检索超时
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 全流程有哪几步?

用户自然语言 → ① 意图识别(选数据源、判断查询类型) orchestrator.go:216 → ② 上下文检索(顺序:OAG → 本地本体 → RAG) orchestrator.go:241-310 → ③ Text2SQL(LLM 生成 SQL,带方言标注) text2sql.go:113-186 → ④ 安全执行(连接器 + RLS/CLS 裁剪) executor.go:70-115 → ⑤ 结果解释 / 可视化

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一个典型工作流长什么样?

trigger(cron: 每天 09:00) → query_data(查昨日超期订单,带 RLS) → condition(超期数 > 0 ?) ├─ true → ai_analysis(生成处理建议) │ → execute_foundry_action(对高风险订单打标/加急) │ → send_notification(feishu: 推送到运营群) └─ false → send_notification(internal: 记录无异常)

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 / ColumnSchemaplatform/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 Hubproducts/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 合规名单 mockproducts/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 / TADwiki/**/PRD-v4.md、wiki/**/TAD-v4/、wiki/upgrade-v5/

15深度链路 A:一次动作执行的完整旅程

这一章把「行动系统」最重要的一条链路——从用户/AI 下决定到外部数据库被真实改写——拆成 12 个可观测的步骤。每个步骤都标出「看哪段代码」和「失败会怎样」,方便你排障。

用户 / AI Copilot / 工作流节点 │ ① 发起动作执行请求(动作名 + 参数 + 目标对象) ▼ [动作目录] ontology.actions 定义(edit_type: create/modify/delete/link) │ ② 解析动作类型,确定编辑模式 ▼ [writepath.Executor.Execute] pipeline.go:154 │ ③ 按 mode 分派:VALIDATE / RUN / ASYNC / VALIDATE_AND_EXECUTE ▼ [step 1] Schema 校验 —— 参数与本体属性定义是否匹配 ▼ [step 2] stepRBAC —— 逐动作的角色权限检查 ▼ [step 3] stepRecordScope —— 记录级 RLS:这次操作允许影响哪些行 ▼ [step 4] stepPropertyPermission —— 属性级三分权限(读/写/编辑) ▼ [step 5] 乐观锁检测 —— pipeline.go:414-443,冲突返回 409 ▼ [step 6] 幂等登记 Begin/Finish —— idempotency.go,防重试重复写 ▼ [step 7] conn.ExecWrite —— pipeline.go:402,真实 SQL 执行 ▼ [step 8] stepAudit(不可跳过)—— 强制写 audit_logs ▼ [step 9] publishChangeEvent —— pipeline.go:1286-1342 → object_change_events ▼ AIP EventScheduler 每 30s 拉取 → 匹配 filters → 触发下游工作流(闭环)

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 动作参数定义
动作执行返回 403Step 2/3/4 权限拒绝RBAC 授权、RLS 策略、属性权限
动作执行返回 409Step 5 乐观锁冲突是否有并发写同一对象
动作「成功」但数据没变mode 是 VALIDATE,或 Step 7 写到别的库动作 mode 配置、数据源连接串
同一动作被执行两次Step 6 幂等登记异常idempotency 记录、Begin/Finish 日志
下游工作流没被触发Step 9 事件未生成,或 scheduler 游标卡住object_change_events、workflow_event_cursor

16深度链路 B:一次提问的完整旅程

这一章拆解「用户在 AIP 里问一句自然语言」到「看到答案」的全过程,重点在 OAG 与权限的介入点。

用户输入:「上个月华东区销售额是多少?」 │ ① HTTP POST /api/v1/chat ▼ [意图识别] orchestrator.go:216 │ → 输出 dataSourceName(选哪个数据源)、查询意图分类 ▼ [上下文检索] 三段式优先级 orchestrator.go:241-310 │ ├─ ②-A OAG 对象路(enabled=true 且命中) │ → oag/client.go:202-239 Search │ → POST Foundry /api/v1/ontology/semantic-search(透传用户 token) │ → 返回本体元数据(object/property/metric 口径,无实例数据) │ → oag/service.go 归组→裁剪→applyColumnMap→按 token 预算贪心渲染 │ ├─ ②-B AIP 本地本体(OAG 未命中/未启用) │ → ontology/service.go:386-494 BuildOntologyContext │ → 读本地 ontology_object_types/properties/link_types(含 JOIN 条件) │ └─ ②-C RAG 多路兜底 → 向量检索相关表/列/文档 │ ▼ [Text2SQL] text2sql.go:113-186 │ → 物理 schema + OAG 语义 并联注入 prompt(orchestrator.go:259-263) │ → prompt 内标注目标方言(mysql/postgresql/sqlite) │ → LLM 生成 SQL;模板约束「OAG 只是口径参考」(templates.go:39) ▼ [安全执行] executor.go:70-115 │ → connectorCache.GetConnector(ctx, dataSourceName) │ → conn.TransformSQL(方言转换) │ → 叠加 RLS(行级)/ CLS(列级)裁剪 │ → conn.ExecuteSQL ▼ [结果处理] → 自然语言解释 / 图表可视化 / 落 nlq_query_log ▼ 返回给用户

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 能查

  1. POST /api/v1/datasources 注册数据源(类型选 POSTGRESQL/MYSQL,填连接串)。密码自动 AES-GCM 加密落库。
  2. POST /api/v1/datasources/:id/test 验证连通性。
  3. POST /api/v1/datasources/:id/import-metadata 导入表/列元数据(注意:导入前会清理旧元数据,避免残留表名误导 RAG)。
  4. 给用户开查询权限(RBAC),配置必要的 RLS/CLS 策略。
  5. 在 AIP 提问验证。若走 OAG,记得把数据源名加进 foundry.oag.data_source_map。

坑:SQLite 需先 ensureDir;连接串里库名写错会 test 通过但 import 失败;权限没配会返回空结果而不是报错。

场景二:把一张物理表变成可查询的本体对象

  1. 在 Foundry 定义 ObjectType:DataSourceID 指向数据源,BaseTable 指向物理表。
  2. 定义 Properties:每个属性用 MappedColumn 映射到物理列,data_type 与列类型对齐。
  3. 定义 LinkTypes 描述对象间关系(如 Order → Customer)。
  4. (可选)定义 Metric 带口径公式,供 OAG 注入。
  5. 用 POST /api/v1/objects/:id/query 验证实例查询能通。

坑:data_type 与物理列类型不一致会导致查询翻译失败;列的改动记得同步更新本体映射。

场景三:让「数据一变更就自动处理」

  1. 在工作流里加 trigger 节点,type 设为 event,配置 filters(匹配哪些对象、哪些变更类型)。
  2. 配置后续节点:query_data 取详情 → ai_analysis 生成判断 → condition 分支 → execute_foundry_action 或 send_notification。
  3. 发布工作流(草稿态不生效)。
  4. 让 Foundry 侧产生一次对象变更,观察 AIP 的 EventScheduler 在 30s 内拉取并触发。

坑:忘了发布;filters 写太严匹配不到;Foundry 不可达时事件拉取失败(看日志);游标卡住会导致事件堆积。

场景四:外部系统主动推数据进来触发处理

  1. 工作流 trigger type 设为 webhook,系统分配一个 /webhooks/:name 路径。
  2. 外部系统带 X-Webhook-Secret 调用该路径。
  3. 工作流按 payload 处理(注意:发布期已强制禁用 execute_foundry_action 与 trigger 模板引用)。

坑:这个工作流不能改数据(设计如此,防外部越权);secret 泄漏等于任何人都能触发,要定期轮换。

场景五:用 AI Copilot 创建并运行一个自动化

  1. 在 AIP 的 Automation 里对话,让 Copilot 用 create_automation 创建工作流。
  2. Copilot 调用 run_automation_now 立即触发验证。
  3. 查看执行记录确认结果。

坑:Copilot 能调的接口是 automation/catalog 里的白名单;写类接口限 admin,普通用户问「帮我改数据」会被拒。

场景六:给外部 AI 客户端开 MCP 接入

  1. 部署 products/server 作为 Hub(独立于本地产品包)。
  2. 本地 AIP 实例通过 /action/ws 出站注册到 Hub。
  3. 外部 AI 客户端用 GET /action/mcp 建 SSE,用 POST /action/mcp 发 JSON-RPC,Bearer=client_key 鉴权。
  4. 客户端可调六个工具(列数据源、NLQ 查数、列/触发/查工作流)。

坑:workflow_run 是真实副作用,client_key 必须严格保管、白名单收紧;Hub 只是中继,业务在后端进程内。

场景七:配一次外部接口调用(调客户 ERP)

  1. 在工作流里加 call_api 节点,填目标 URL、方法、请求体模板。
  2. 设定超时(默认 15s)与响应上限(1MiB)。
  3. 注意失败非致命——需要「失败也要处理」的话,用 condition 检查节点输出。

坑:没有连接器抽象,凭证需自行处理(放 header);URL 禁止引用 trigger 模板(防 SSRF);响应超 1MiB 会被截断。

场景八:排查「查询结果不对」

  1. 查 nlq_query_log 拿实际执行的 SQL。
  2. SQL 错 → 查上下文:OAG 是否命中(看日志)、元数据是否过期、column_map 是否缺失。
  3. SQL 对但结果少 → 查 RLS/CLS 是否过度裁剪。
  4. SQL 对但结果空 → 查数据源是否连到正确的库(FOUNDRY_DEMO_DB_PATH 类环境变量是否同步)。
  5. 都正常 → 查数据同步链路(数据集是否刷新)。

坑:测试隔离时环境变量分库,写路径可能连到旧的演示库;这是「本机看着对、测试环境不对」的经典原因。

场景九(附加):本机起一套完整环境

  1. 确认 PostgreSQL 可用(F:\PostgreSQL\17\bin),建库 chatbi_action_dev。
  2. 在 action/ 目录下按顺序起五个产品:./bin/foundry.exe、./bin/aip.exe(cwd 必须是 action/)。
  3. 逐个 curl --noproxy '*' .../health 确认 200。
  4. 起前端:cd action/web && npm run dev(Vite 5173,代理按前缀分流)。
  5. 注册/登录(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 warncolumn_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.dbbootstrap.go:147-200:PG ping 失败降级 SQLite
Apollo优先 PG(注释写「与 foundry 共用」)./data/apollo_platform.dbbootstrap.go:53-160 同构
Gotham优先 PG./data/gotham_platform.dbbootstrap.go:54-190 同构
Swift优先 PGdata/swift_platform.dbbootstrap.go:39-70 同构
AIP默认禁用 PG(需 AIP_ENABLE_PG=true)data/platform.dbbootstrap.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())。

所以「五产品共用一个 platform.db」这个前提不成立:SQLite 模式天然是五份独立文件(这也是为什么默认 AIP 用 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,含逐条源码证据)。已修复与仍存在的清单:

#问题状态
1api_keys:foundry/apollo 同表不同列、互相可见、撤销跨产品失效已修复 apollo 改表名 apollo_api_keys(2026-09-05)
2agent_tasks 双定义主键异构(AIP string vs Apollo uint),共表互改即互毁防御已加 AIP 启用 PG 时冲突检测拒绝接入;彻底隔离仍需 aip_ 前缀
3ontology_object_types/ontology_properties 双定义(9 列 vs 17 列)同名互斥防御已加 同上;aip_ 前缀隔离待实施
4审计哈希链四进程共写必分叉已根治 按产品拆表,每产品一条独立链
5审计表无产品列,混表不可按产品过滤已修复 五产品各落 <product>_audit_log
6AUTH_KEY 五产品同 key upsert,授权互相覆盖两面 若产品套件整体授权则正确,否则需加前缀
7data_sources 明文凭据全产品可读已修复 改为 AES-256-GCM 加密落库(platform/crypto/aes.go)
8AIP PG 分支被硬编码禁用、行为与文档不符已修复 改为 AIP_ENABLE_PG 开关 + 冲突检测
9SQLite 降级下身份/授权/可见性静默隔离仍存在 需明确两种拓扑的支持声明
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 隔离),具体要改什么?

  1. 连接串:所有产品 DSN 加 search_path=<product>,platform,或建连后 SET search_path。
  2. 原生 DDL:约 14 处(各产品 bootstrap 里的原生 SQL)要加 schema 前缀或确保走 search_path。
  3. 动态建表:3 处(如 db.Table(...) 形式的动态表名)需要显式 schema。
  4. 探测逻辑:information_schema 查询(如 AIP 的冲突检测)要限定 schema,否则会跨 schema 匹配到同名表。
  5. 审计拆表:<product>_audit_log 这套前缀机制可以保留(与 schema 不冲突),或改为 schema 内的统一表名。
  6. 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「现在到底该不该拆」——决策建议。

  1. 若目标是单机单租户演示/交付:不拆。维持现状(PG 共库 + 表前缀规范化),补完 aip_ 前缀隔离即可。这最小代价保留了统一身份与统一授权的产品特性。
  2. 若目标是对外多租户/多客户部署:考虑 方案 B(schema 隔离),改动集中在 platform/database + 各 bootstrap.go,工作量可控且保留统一身份。
  3. 若目标是产品完全独立发售:才考虑 方案 C(库隔离 + API),但要接受重建 SSO/统一授权的成本。
一个共同的隐藏成本:无论选哪种,都要补一条「多产品同拓扑集成冒烟」测试。现状单包测试全用独立 SQLite + 精选迁移清单,任何共库冲突在测试中不可复现,只有五进程同机启动才暴露。这是拆与不拆都必须补的基础设施。

Q243不拆的话,最该做的三件事是什么?

  1. 补 aip_ 前缀隔离(agent_tasks / ontology_object_types / ontology_properties)——这是唯一残留的「定时炸弹」,完成后 AIP 才能安全启用 PG。
  2. settings key 加产品前缀规范(如 workflow_event_cursor → aip_workflow_event_cursor),AUTH_KEY 保持全局但写明「平台授权」语义。
  3. 加「五产品 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 隔离(中等工作量、保留统一身份),彻底分库是架构级改造。」