Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
143 changes: 143 additions & 0 deletions 20-product-tdd/core-app-boundary.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
# Core 与 App 边界

已确认的目标设计,适用于各语言实现;不表示现有实现已完成迁移。具体包名、目录、类和构建方式由各实现拥有。

## 目的与判断标准

Core 是可被独立程序消费的 InKCre SDK/lib。调用者应能使用其公开接口完成支持的协议和领域操作,不必启动第一方应用,也不必继承第一方应用的窗口、服务器、全局配置或后台任务。

App 是可运行产品的装配入口,拥有运行环境、资源生命周期、默认策略和用户体验。HTTP 服务、CLI、桌面程序、Web 应用与浏览器扩展都可以是 App;这里的 App 不等于知识活动中的“应用(application)”。

Extension 继续拥有具体业务能力和契约。第一方、默认安装、被多个 App 使用,都不自动使一项扩展业务成为 core。某个扩展本身也可以提供库与平台 UI。

划分一段实现时,回答三个问题:

1. 换一个应用入口后,这项协议或业务保证是否仍必须保持?若是,它属于 core 或相应 Extension 的可复用实现。
2. 它决定的是应用怎样启动、当前选用哪个配置和怎样展示吗?若是,它属于 App;扩展自己的业务交互仍由 Extension 拥有。
3. 调用者只是想完成一次领域操作,却必须理解第一方应用的配置、全局状态或启动流程吗?若是,当前边界仍未分离。

Core 可以执行 I/O、持有缓存和运行任务,也可以直接使用其声明支持的平台 API。分离要求其依赖、作用域和资源所有者清楚,而非把 core 限缩为 DTO 与纯函数。独立于第一方 App,不等于承诺跨平台。

## 与现有产品模型的关系

沿用 [Unit topology](unit-topology.md)、[System state and authority](system-state-and-authority.md) 和 [Knowledge capability contract](knowledge-capability-contract.md)。Core/App 是交付与依赖边界,不增加共享状态权威,不改变采集、整理和应用的业务边界。

一个实现仓库可以交付库、多个应用和扩展;一个运行中的应用可承载 Peer。Core 库本身不因被安装或导入就成为在线 Peer。各语言实现也不构成一个必须同时发布或同时运行的中央系统。

数据库 schema 与迁移仍由 impl-py 拥有,所有 Peer 遵守同一个 [Peer database runtime contract](peer-database-runtime-contract.md)。使用哪种语言、哪种数据库传输或哪一种 App,不改变已认证 Peer 的协议权限。

## 责任分配

| 责任 | Core 的库能力 | App / Extension |
| --- | --- | --- |
| 协议与数据模型 | 已支持合同的类型、编码、错误和准入检查;公开兼容范围 | 选择目标部署、读取配置、呈现错误与恢复入口 |
| 信息库操作 | 图读写、内容 hydration、Storage/Resolver 合同与相应实现;事务边界跟随业务保证 | 页面布局、导航历史、预览容器与用户操作编排 |
| Peer 与能力 | 公告、租约、精确能力发现和调用;保留未知结果等失败语义 | 是否参与、身份来源、实际启用哪些能力、何时公布在线 |
| Job/Cron 与执行 | 提交、领取、状态转换、取消及可复用执行机制 | 是否运行 worker、调度唤醒频率、并发预算、进程退出协调 |
| Extension Host | 可复用加载/启停机制、兼容性检查、注册与释放规则,可独立选用 | 第一方分发组合、进程内安装/重启策略、平台资源和 UI 挂载 |
| 具体业务 | 已由产品确立的通用领域能力可按需提供;不强制各语言实现同样广度 | Capture、Mail、Local Files 等具体业务由相应扩展拥有 |
| 凭据与连接 | 接受明确配置与凭据;执行认证;有实际复用价值的凭据/连接持久化可以直接作为库能力 | 应用环境读取、当前选中连接、表单与授权交互政策 |
| 可观测性 | 传播上下文、记录库操作的事件;遵守既有内容边界 | 安装 exporter、选择目的地和资源预算、管理进程观测生命周期 |
| 运维与发布 | 声明需要的协议范围;提供普通运行所需的协议类型 | 数据库初始化/升级、部署、打包、签名、公证与服务启动 |

产品架构只规定 Core/App 的消费方向,并保留 Extension 的业务所有权,不增加统一的“平台适配层”。代码使用平台 API 本身不是归属判据:数据库驱动、Keychain 或浏览器存储可以实现一项有用的库能力;它们是否进入 core,取决于公开用途、依赖代价和支持的平台。

“App 拥有”不要求把代码内联进可执行文件。应用内部可以有清楚的模块;多个应用确有共同 UI 消费时,可以复用既有 UI 包或提取有具体职责的库。新增公共接口、包和替换点需要实际消费者,不预设每种实现都配一套 adapter。

标准 Resolver 的解释、文本或字节投影可以属于 core;应用的 Vue/SwiftUI 组件、DOM 渲染和视图状态归 App。导航目的地可以是无 UI 框架的领域值,历史和窗口由应用实现。具体扩展的 renderer 仍跟随扩展。

## 依赖与交付

```text
App ───────────────→ Core 公共接口
│ ↑
└─→ Extension ───────┘

Core:领域操作、协议、数据访问、可复用 Host/执行机制
App:入口、交互、配置选择、启动退出与运行政策
```

这是责任图,不要求每条边都对应一个独立发布包。最小交付是一个可独立消费的 core 库与一个引用它的 App。Host/worker 可以是 core 内按需使用的能力;独立模块或包由真实依赖和消费者决定,不增加一个固定的“可选运行层”。已有独立 runtime 的合同 owner 保持不变。

- 基础 core 不导入第一方 App;普通数据与领域 API 不强制 UI 框架、Web 服务入口或扩展安装器,也不反向取得 App 的默认全局实例。
- 数据库驱动、HTTP 客户端、序列化和领域计算依赖可以属于相应库模块。平台限制必须真实声明;本契约不承诺每种语言的库立即支持所有操作系统。
- 公共入口不能通过重导出间接拉入全部可选功能。包清单、构建产物和运行时依赖都必须支持实际的独立消费。
- App、Extension 和外部调用者使用明确的公共接口。仓库内部更低层实现不因提取而自动成为长期公共 API。
- Core 的源代码版本、App 发布版本、Extension Host ABI、Extension Release 和数据库协议版本各有意义,不能用其中一个替代另一个。
- 各语言按已有生态交付库,不要求统一类名、目录树、FFI 或跨语言代码生成。共享一致性来自合同与观测结果。

## 配置、实例与生命周期

调用方显式创建操作所需的客户端或上下文,绑定目标部署、传输、凭据来源和已选能力。Core 内部传递已验证的类型,不重复依赖 App 的环境读取和表单校验。

导入或链接基础 core 不读取必填应用环境、不连接数据库、不监听端口、不发布 Peer、不启动定时器、不安装扩展,也不触发权限 UI。对象构造可以建立本地状态;I/O 与后台执行通过公开、显式的操作或生命周期开始。

资源创建者负责释放;借用外部连接、session 或 executor 的接口必须说明借用范围,不擅自关闭外部资源。库拥有的连接池、订阅和任务需要可等待的关闭边界。App 可以用一个实例组合整个进程,但进程单例是 App 的选择,不是 core 模块导入时的事实。

实例的目标部署不随 App 的“当前选中连接”隐式变化。切换连接时,由 App 决定关闭旧执行、建立新上下文并切换界面。已开始的命令及持久待办保持原目标;共享状态提交结果不因窗口关闭或 UI 切换而改变归属。缓存、注册表和认证状态不得把另一个实例的部署数据或凭据混入当前调用。

静态、不可变合同定义可以全局共享。可变运行资源应归一个明确上下文。无需为此建立通用依赖注入容器或让所有函数都增加相同的 context 参数;各实现采用能保持所有权的普通构造和组合。

## Host 与执行能力

把 Host 视为一项可嵌入的库能力。App 可以选择使用 Host API 启动扩展,也可以只使用数据访问能力。Core 的一次普通读写不以恢复所有扩展或启动 worker 为前置条件。这里的可选是“是否使用/启动”,不预先要求拆出独立包。

Host 的机制与 App 的装配分别拥有以下保证:

- Host 维护注册、启动、停止和资源释放;App 把路由与窗口接入应用入口,Extension 拥有其业务贡献。支持平台上的加载和资源机制可直接由 Host 实现。
- App 决定加载哪些分发、提供哪些平台服务,按宿主生命周期调用 Host。
- 扩展只有依赖条件具备且已实际启动,才能公告可执行能力;停止或失败时撤销能力并收束自己的资源。已安装、已启用与正在运行仍是不同事实。
- 无界面的执行者不需要实现“显示主窗口”。平台 UI contribution 可以单独存在,业务生命周期不得强制携带窗口方法。
- 扩展加载机制可依赖自己的语言运行时。已有独立 Extension SDK/runtime 的合同继续由原 owner 维护,不因本次 core 提取复制第二套生命周期或版本协议。

基础库、可选执行模块与应用的界面边界不能把现有业务拆成远程调用链。支持本地执行的 Peer 可以直接调用;跨 Peer 委派只在既有能力合同要求的条件下发生。

## 调用与失败路径

### 独立程序读写信息库

调用方提供配置 → 创建数据库客户端 → 验证必要协议准入 → 调用图操作 → 得到已提交结果或准确失败 → 释放自己创建的资源。

这条路径不启动 UI、HTTP 服务或扩展扫描。读取已有图不因相应 Extension 未运行而失去数据访问权;解释内容时,缺失精确 Resolver 则返回既有的 unknown/unsupported 结果。写入的事务与输入校验由相应接受边界负责。

### 应用运行一项扩展能力

App 创建连接和 Host → 选择分发并提供平台能力 → Host 启动扩展 → 发布实际就绪能力 → 处理命令 → 停止接收新工作 → 收束执行与注册 → 关闭所属连接。

启动失败不产生“已在线”的公告;停止观察不等于取消工作;取消不承诺回滚已经提交的事务或撤回外部效果。关闭顺序由实际资源依赖决定,不要求所有实现采用同一启动函数。

### 提交后断线或应用退出

传输层不得把“没有收到回复”统一翻译成“未执行”。Core 保留结果未知与明确拒绝的区别;可恢复业务根据自己的幂等身份、回执或共享事实查证。具体材料、重试节奏与本机待办由相应业务和 App 拥有,不为分层新增一个全产品离线队列。

## 实现广度与兼容演进

各实现可以只提供已实现的能力,但必须明确可用 API、传输与执行限制。Peer 的完整协议权限不代表每种语言 SDK 都已封装全部操作,更不代表都具备全部执行能力。未来实现可以从最小可用场景开始,无须复制最丰富实现的全部模块。

移动源码、改包名和拆适配不得悄悄改变共享字段、错误含义、事务、Job 状态或 Extension 协议。共享合同变更另行版本化;数据库迁移仍只有既有 owner。

已发布公共 API 的拆分需要对真实消费者评估兼容性。可保留一个过渡的应用/框架适配入口,或按已有版本政策发布破坏性变更;不可为了保留旧入口,让新的基础 core 再次依赖 UI 和默认应用状态。未发布内部符号可以随当前消费者直接迁移。具体兼容窗口属于对应库的发布方案。

## 验收条件

各实现通过以下条件证明边界成立;设计确认本身不构成实现通过的证据:

| 要证明的边界 | 观察方式 | 不合格表现 |
| --- | --- | --- |
| SDK 可独立消费 | 在没有 App 源码和 workspace 源码别名的消费项目中使用构建产物;Swift 用独立 package 消费 library product | 需要导入 App、复制源码或依赖私有目录才能工作 |
| 基础导入无启动作用 | 不提供 App 环境变量、不加载 UI/服务入口,导入并构造纯配置 | 读取必填应用配置、访问网络、启动任务或触发授权交互 |
| 具有真实可用能力 | 独立消费者连接一次性数据库,完成一个图读写场景;另一种语言读取其持久结果 | 只有类型可用;读写依赖第一方服务或私有应用状态 |
| 目标与资源有明确所有者 | A 的调用未完成时切换应用到 B;结束 A;关闭后检查所属资源 | 请求改投 B、A 的缓存/凭据进入 B、停止 A 破坏 B 的外部资源 |
| 可选运行能力可省略 | 不启用 Host/worker 的图消费者正常工作;无 UI 扩展能完整启停 | 一次图读取要求扩展恢复,或 worker 必须实现窗口接口 |
| App 仍是完整产品 | 原应用通过其 core 公共接口完成原有关键旅程与恢复 | 独立 SDK 可用,但 App 保留另一套业务实现或失去既有恢复语义 |
| 分离没有改变协议 | 复用现有跨语言合同样例与失败场景,验证相同共享结果 | 包名变化导致共享身份、事务、图形状或错误语义漂移 |

实际验证遵守各仓既有测试政策。一次性数据库场景不能连接共享开发库或预览库。原生权限、真实 UI 与第三方集成只在受影响时做对应验收;编译与导入检查不能代替这些证据。

## 取舍

只搬目录能获得视觉分层,但不能让外部程序摆脱全局配置与 App 启动。只抽模型/协议包则会迫使每个 App 继续重复事务、执行与恢复语义。把所有公共代码塞进一个完整 runtime SDK 又会强迫只读消费者加载 UI、Host 和后台执行。

采用可独立消费的 Core 与直接使用它的 App。平台适配与数据库驱动抽象可以带来替换和复用收益,但当前需求不足以覆盖新增接口、装配、验证和维护的成本。依赖注入可以降低部分装配成本,仍需证明替换点的实际价值,因此不统一引入平台适配层,也不要求每种具体实现都配一个抽象接口。

各实现按责任逐项判断归属:平台实现可以直接进入有明确用途的库,App 保留自己的交互与运行政策。当真实的多实现替换或跨消费者复用使收益超过成本时,再在对应能力处建立必要边界。是否拆独立包由依赖和消费者决定,不以未来语言数量预设模块数。
4 changes: 4 additions & 0 deletions 20-product-tdd/cross-unit-contracts.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Record durable data and behavior contracts that span more than one unit reposito
- This file is initialized as a scaffold.
- Add only contracts that cannot be safely inferred from one unit's code alone.

## Core 与 App 边界

[Core 与 App 边界](core-app-boundary.md)拥有各语言 SDK/lib 的独立消费、依赖方向、资源生命周期与演进约束。平台实现按职责和实际收益归属,不要求统一适配层;具体目录、包名及迁移由各实现拥有。

## Peer Database Runtime Contract

- All units that directly operate shared persisted state consume the canonical
Expand Down
10 changes: 8 additions & 2 deletions 20-product-tdd/unit-topology.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,16 @@ Define cross-unit technical boundaries and ownership so changes do not leak resp
- This file is initialized as a scaffold.
- Add stable cross-unit boundaries only after they are confirmed by at least one real integration change.

## Core 与 App

[Core 与 App 边界](core-app-boundary.md)规定各语言实现的 SDK/lib 与应用消费关系,以及资源生命周期和平台依赖的归属。这是已确认的目标设计,各实现的迁移状态由对应仓库记录。

实现仓库可交付库、应用和扩展;运行中的应用可承载 Peer。库本身不因导入而成为在线节点,不预设统一的平台适配层。

## Database Peer Topology

- `impl-py`, `impl-ts`, `impl-swift`, and future units are peer nodes; frontend/backend hierarchy is not
part of the durable topology.
- Applications delivered by `impl-py`, `impl-ts`, `impl-swift`, and future implementations may run as
peer nodes; frontend/backend hierarchy is not part of the durable topology.
- Native PostgreSQL and PostgREST are transports over one admitted database protocol, not
separate semantic authorities.
- Every authenticated peer may operate the complete admitted protocol surface. Different
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

- [Product requirements](../10-prd/index.md)
- [Unit topology](../20-product-tdd/unit-topology.md)
- [Core 与 App 边界](../20-product-tdd/core-app-boundary.md)
- [Security boundary model](../20-product-tdd/security-boundary-model.md)
- [System state and authority](../20-product-tdd/system-state-and-authority.md)
- [Cross-unit contracts](../20-product-tdd/cross-unit-contracts.md)
Expand Down
Loading