# OIP-Book **Repository Path**: orginone/OIP-Book ## Basic Information - **Project Name**: OIP-Book - **Description**: OIP-Book是奥集能开放架构的 中心化认证服务端,承载 OIP 协议核心实现——所有权利的签发、转移、验证、撤销、合规检查均在 OIP-Book中完成。OIP-Book全局唯一部署,所有 GoInOne节点共享同一个 OIP-Book完成认证、权限查询和 OIP 协议操作。 - **Primary Language**: Rust - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 2 - **Created**: 2026-09-05 - **Last Updated**: 2026-10-20 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # OIP-Book · 权利状态机层服务 > **文档版本**:v1.1 | **更新**:2026-09-26 | **状态**:正式发布 > **关联**:[OIP-Book-Design.md](../OIP/docs/OIP-Book-Design.md) · [OIP-Protocol.md](../OIP/docs/OIP-Protocol.md) · [OIP.md](../OIP/docs/OIP.md) `OIP-Book` 是开放互联协议(Open Interconnect Protocol, OIP)的 **中心化认证服务端**,承载 OIP 协议核心实现——L3 权利状态机层。所有权利的签发、转移、验证、撤销、合规检查均在 `OIP-Book` 中完成;`OIP-Book` 全局唯一部署,所有 `OIP-GoInOne` 节点共享同一个 `OIP-Book` 完成认证、权限查询与 OIP 协议操作。 > Rust 实现(edition 2024;axum + tokio + serde + ed25519-dalek + mongodb + redis + async-nats + tokio-postgres),对齐 OIP 规范仓库中的设计文档 [OIP-Book-Design.md](../OIP/docs/OIP-Book-Design.md)。 ## 核心能力 | 能力 | 说明 | | --- | --- | | 可验证权益凭证(VET)生命周期状态机 | Issued/Active/Transferred/Consumed/Expired/Revoked/Dead 七态受检转移 | | 策略决策点(PDP)分阶段校验 | 生命周期→签名→操作集→nonce→委托→约束→客体→设备→决策;全部拒绝路径统一返回 `OIP_4031`(布尔盲化) | | 四大转移法则 | Override / Workflow / Exchange / Collaboration,含工作流审批与锁定-提交 | | 委托链(Delegation Chain) | 委托策略校验(收缩、深度)与级联消亡(Cascade Revocation) | | 默克尔审计树(Merkle Audit Tree) | append / root / proof / verify 四操作,排序对哈希保证确定性 | | 合规引擎 | 制裁清单(Sanctions)+ GDPR 跨境数据主体检查(DPIA) | | 全局断路器(Global Circuit Breaker) | `force_level` ≥ `breaker.force_threshold`(默认 250)触发,HSM 签名验证监管指令 | | 去中心化标识符(DID)管理 | 注册 / 状态管理(Active / Suspended / Deactivated) | | Ed25519 签名 | VET 签发与委托链验签(canonical payload) | | 存储抽象 | **PostgreSQL(`store.mode=postgres`)** / MongoDB(`store.mode=mongo`) / 内存(`store.mode=memory`,测试用);共表面另支持 **MySQL(`OIP_BOOK_ORG_STORE=mysql`)** | | 持久化覆盖 | `postgres` 模式下 VET / 组织协同 / Exchange 锁 / 审计+Merkle 根 / 工作流 / 同步游标 / 第三方绑定 / 发件箱全部落库(账号密码凭据暂为进程内存储,其 PostgreSQL 后端为后续项) | | 事件总线 | 内存 / NATS JetStream 双实现(`events.mode`) | | 发件箱(Outbox) | 权威源与事件的原子双写 + 异步补投;超重试上限转死信且不删除 | | 限流 | 令牌桶(`src/ratelimit.rs`:Issue 100/s、Verify 1000/s、Revoke 10/s、Override 1/s),配额落在认证、VET 签发/验证/撤销、Override 转移与数据分享端点 | | **组织协同** | Target/Team/Relation/Identity/Authority,按 `OIP-Book-Design.md` §5B.2 归一为 DID 实体 / VET 映射 | | **办事流程** | Workflow Admin(§5A.8):定义/实例/任务/网关管理面 | | **System Auth** | 登录态端点(§5A.7):Login/Refresh/Logout/Challenge 签发面 | | **账号密码凭据(权威面)** | 账号注册 / 账号密码登录 / 更改密码 / 外部断言兑换 / OTP 私钥托管(§5A.9 `QueryOtpPrivateKey`);凭据与 DID 一一对应,密码以 argon2 PHC 存储 | | **节点间数据同步** | SyncSnapshot(§5B.7):DID/VET/审计/Merkle 根/断路器全量兜底 + 游标增量 | | **第三方账号绑定** | `vet/bind`、`vet/unbind`、`vet/bindings/:target_did`(§5A.10) | ## 快速开始 ```bash # 1. 准备配置(模板见 config.example.yaml) cp config.example.yaml config.yaml # 编辑 config.yaml:填写 store / events / hsm_key 等 # 2. 构建 / 测试 / Lint cargo build --release --locked cargo fmt --all --check cargo clippy --all-targets -- -D warnings cargo test --all-targets # 单元测试 + tests/auth_issuance_contract.rs 契约集成测试 # 3. 运行(可用 OIP_BOOK_CONFIG 覆盖配置路径) OIP_BOOK_CONFIG=config.yaml cargo run ``` ## 环境变量 | 变量 | 作用 | 缺省行为 | | --- | --- | --- | | `OIP_BOOK_CONFIG` | 覆盖配置文件路径 | 使用 `config.yaml` | | `OIP_BOOK_INTERNAL_TOKEN` | 启用入站内部令牌门(≥16 字符) | 未配置或为空则不启用 | | `OIP_POSTGRES_URL` | `#[ignore]` 集成用例的 PostgreSQL 连接串 | 用例打印 `skip` 并返回 | | `OIP_REDIS_URL` | `#[ignore]` 集成用例的 Redis 连接串 | 用例打印 `skip` 并返回 | | `OIP_BOOK_DATABASE_URL` | `store.mode=postgres` 时的 PostgreSQL 连接串(部署期注入,凭据不进版本库) | 回落到 `store.database_url` | | `OIP_BOOK_ORG_STORE` | 覆盖**共表面**(组织协同 / 办事流程 / 私有数据)存储模式(`memory`/`mongo`/`postgres`/`mysql`) | 回落到 `store.mode` | | `OIP_BOOK_MONGO_URI` | `OIP_BOOK_ORG_STORE=mongo` 时的 MongoDB 连接串 | 回落到 `store.mongo_uri` | | `OIP_BOOK_MYSQL_URL` | 共表面 MySQL 连接串(`OIP_BOOK_ORG_STORE=mysql` 时使用) | 回落到 `store.mysql_url` | | `OIP_BOOK_PRE_STOP_SECS` | 优雅停机的**退流窗口**秒数(收到 `SIGTERM`/`SIGINT` 后先置未就绪、再排空在途请求前的等待) | `5`(`0` 表示不等待) | ## 配置键与默认值 `src/config.rs` 以 `#[serde(deny_unknown_fields)]` 严格解析,未列出的键会被拒绝。默认值取自 `src/config.rs`,模板见 `config.example.yaml`。 | 键 | 默认值 | 说明 | | --- | --- | --- | | `listen` | 无(必填) | HTTP 监听地址,模板为 `"0.0.0.0:8081"` | | `log_level` | `"info"` | tracing 过滤级别 | | `store.mode` | `"postgres"` | `memory` / `mongo` / `postgres`;决定 **OIP 自有面**(VET / 审计 / 锁 / 发件箱 / DID / 凭据 / 游标 / 绑定)的落点,未知值在 `AppState::from_config` 显式报错(含 `mysql`:MySQL 只承载共表面,报错会把调用方引到 `OIP_BOOK_ORG_STORE=mysql`)。默认取生产首选 `postgres`:漏配时**启动期显式报错**(未配 `store.database_url`),而非静默降级到 `memory` | | `store.mongo_uri` | `""` | `store.mode=mongo` 时必填,否则启动失败 | | `store.redis_uri` | `""` | `store.mode=mongo` 时必填(nonce 原子消费),否则启动失败 | | `store.database_url` | `""` | `store.mode=postgres` 时必填,否则启动失败 | | `store.mysql_url` | `""` | 共表面落 MySQL 时的连接串(`mysql://user:pass@host:3306/relation`);`OIP_BOOK_ORG_STORE=mysql` 时必填,否则启动失败 | | `events.mode` | `"memory"` | `memory` / `nats`;未知值显式报错 | | `events.nats_uri` | `""` | `events.mode=nats` 时必填,否则启动失败 | | `breaker.force_threshold` | `250` | 全局断路器触发的 `force_level` 阈值 | | `hsm_key` | `null` | Base64 编码的 Ed25519 私钥,用于验证监管签名 | | `demo_mode` | `false` | 为 `true` 时允许缺失 `hsm_key` 启动(演示用);`false`(生产)时缺 `hsm_key` **或** `internal_token` 任一即**拒绝启动**。判据单点收口在 `Config::check_production_gates`——**独立启动与嵌入装配(`AppState::from_config`)走同一条**,嵌入方不会绕过 | ## 持续集成 `.github/workflows/ci.yml` 在 push(`master`/`main`)与 `pull_request` 时执行,运行于 `ubuntu-latest`,工具链 Rust `1.98.1`(含 `rustfmt`、`clippy`),并以服务容器提供 `postgres:16`(库 `oip_test`)与 `redis:7`: ```bash cargo fmt --all --check cargo clippy --all-targets -- -D warnings cargo test --all-targets cargo test --lib -- --ignored # PostgreSQL / Redis 集成用例(服务容器注入 OIP_POSTGRES_URL、OIP_REDIS_URL) cargo build --release --locked ``` `.github/workflows/contract-check.yml` 执行协议面漂移校验,且为 **fail-closed**:若同级目录不存在 `../OIP/tools/gen_protocol.py`,先尝试 `git clone --depth 1 https://gitee.com/orginone/OIP.git ../OIP`;克隆后仍不可用则报错退出(非跳过)。可用时执行 `python3 ../OIP/tools/gen_protocol.py --check`,任何漂移即失败。`src/protocol_gen.rs`、`src/topics_gen.rs` 由该脚本生成。 ## 持久化模式 `store.mode` 决定 `OIP-Book` 的**权威源数据(事实与状态)**落库方式: > **两个平面**:本键只管 **OIP 自有面**(VET / 审计+Merkle 根 / Exchange 锁 / 发件箱 / > DID / 凭据 / 同步游标 / 第三方绑定)。**共表面**(组织协同 / 办事流程 / 私有数据)由 > `OIP_BOOK_ORG_STORE` 决定、缺省回落本键——三者共用遗留关系核的 `tb_*` 表,MUST 同源。 > 遗留关系核的现网库是 **MySQL 8**,故「接管遗留库」的形态是 > `store.mode=postgres` + `OIP_BOOK_ORG_STORE=mysql`。 | 模式 | 存储位置 | 适用场景 | 说明 | | --- | --- | --- | --- | | `memory` | 进程内 | 单元测试 / 本地演示 | 重启即丢失 | | `postgres` | PostgreSQL(关系数据库) | 生产 | VET、组织协同、Exchange 锁、审计+Merkle 根、工作流、同步游标、第三方绑定、发件箱全部落库(详见下表) | | `mongo` | MongoDB | 生产备选 / 存量兼容 | VET 落 `oip_book.vets`,组织协同落 `org_*` 集合;nonce 经 Redis 原子消费 | | `mysql`(仅经 `OIP_BOOK_ORG_STORE`) | MySQL 8(遗留关系核库) | 接管遗留关系核 / 存量兼容 | 共表面原地落遗留 `tb_*` 共表;OIP 自有面仍按 `store.mode` 另选 | `store.mode=postgres` 时,`store.database_url` 指定连接串(如 `postgres://user:pass@host:5432/oip`),持久化覆盖: | 数据 | 表 | | --- | --- | | VET 生命周期(CAS 乐观锁,`version` 列) | `oip_vet` | | 组织协同(Target/Team/Relation/Identity/Authority) | `tb_target` / `tb_team` / `tb_relation` / `tb_authority` / `tb_identity` / `tb_team_identity` / `tb_id_proof` / `oip_identity_authority` | | Exchange 两阶段锁 | `oip_lock` | | 转让流程实例(Workflow 审批链;CAS,`version` 列) | `oip_transfer_workflow` | | 审计日志 + Merkle 根历史 | `oip_audit` / `oip_merkle_root` | | 第三方账号绑定 | `oip_third_party_binding` | | 发件箱 | `oip_outbox` | | 办事定义 | `oip_wf_define` | | 办事实例 / 任务 / 网关 / 节点记录 | `tb_work_instance` / `tb_work_task` / `tb_work_node_gateway` / `tb_work_record` | | 私有数据(遗留表补齐,暂无消费方) | `tb_private` | | DID 注册表 | `oip_did` | | 节点同步游标 | `oip_sync_cursor` | ### 表结构与遗留关系核共表 组织协同与办事流程的表沿用遗留关系核(`oiocns-goinone/source/schema`)的表名与列名,使遗留库可被就地接管;列级纪律为:遗留列原样保留、同名同义列(`name` / `code` / `remark` / `title` / `create_time` / `update_time`)复用、其余 OIP 列以 `oip_` 前缀附加、`id BIGINT`(遗留内码)与 `oip_did` / `oip_oid`(OIP 标识)双键并存。**OIP DID 与遗留数据链标识 `chainId` 属同一标识空间**,`oip_did` 的回填取该值,不另建 `id ↔ DID` 映射表。 与 Ent 模型对齐:遗留 `tb_*` 表的软删除列 `is_deleted`(NOT NULL DEFAULT false)与办事任务的枚举列默认值(`approve_type` / `task_type` 取 Ent 取值域 `审批` / `事项`)在建表时统一补齐——OIP 不读写这些遗留列,但补齐后 OIP 写入的行仍满足遗留侧过滤与枚举约束,且全新库结构与 Ent 一致。 列级细化:原先以 `data` / `doc` JSONB 承载整条记录的表已逐列化;**列表与树形字段一律落入关系表,不使用 PG 数组列**——跨主体关系落 `tb_team_identity` / `tb_id_proof` / `oip_identity_authority`,团队成员落 `tb_relation` 的成员行,职权层级由 `tb_authority.parent_id` 自关联还原,办事实例审批人由 `tb_work_task` 受理人聚合;仅**形态随取值而变**的变长嵌套载体保留 JSONB(办事定义 `content`、实例与网关 `oip_content`、VET `parameters`、审计/发件箱 `payload`)。映射清单、迁移步骤与已知边界见 [../OIP/docs/OIP-Book-Design.md](../OIP/docs/OIP-Book-Design.md) §6.3。 OIP 自有表 `oip_vet` / `oip_wf_define` 另具备**前向迁移**:启动建表时若检测到历史的单列 `data`(JSONB)(整条记录序列化),自动补列、从 `data` 逐字段回填、收紧约束并删除旧列——旧库无需人工介入即可被接管。 ### MySQL 共表面(`OIP_BOOK_ORG_STORE=mysql`) 遗留关系核的现网库是 **MySQL 8**(Ent 生成结构,`oiocns-goinone/kernel/store/migrate/schema.go`),PostgreSQL 只是其驱动层可选项。MySQL 变体使共表面**原地**落该库,列级纪律与 PostgreSQL 侧完全一致(遗留列原样保留、同名同义列复用、其余 OIP 列 `oip_` 前缀、`id` 与 `oip_did`/`oip_oid` 双键并存)。 方言对应(`src/mysql.rs` 为公共设施): | 关注点 | PostgreSQL | MySQL | | --- | --- | --- | | 幂等写入 | `ON CONFLICT ... DO UPDATE` | `ON DUPLICATE KEY UPDATE ... = VALUES(...)` | | 建表互斥 | `pg_advisory_xact_lock`(事务级) | `GET_LOCK` / `RELEASE_LOCK`(会话级) | | `id` 分配 | `CREATE SEQUENCE` + 列默认值 | `oip_id_seq` 表 + `LAST_INSERT_ID` 原子自增(**不用** `AUTO_INCREMENT`,以免改写遗留列定义) | | 布尔 / 时间 / 嵌套载体 | `BOOLEAN` / `TIMESTAMPTZ` / `JSONB` | `TINYINT(1)` / `DATETIME(6)`(按 UTC 约定)/ `JSON` | | 列表聚合 | `array_agg(...)` | `GROUP_CONCAT(... ORDER BY ... SEPARATOR ',')` | 接管动作与 PostgreSQL 侧同构:补 `oip_*` 列、补 `is_deleted TINYINT(1) NOT NULL DEFAULT 0`、为遗留**NOT NULL 且无 DB 默认值**的列补默认值(`tb_target.type_name` / `tb_work_instance.content_type` / `tb_work_task.title|approve_type|task_type`),并建 OIP 键索引(MySQL 无 `ADD COLUMN IF NOT EXISTS` / `CREATE INDEX IF NOT EXISTS`,故先查 `information_schema` 再执行)。`tb_work_*` 与 `tb_private` 分别由 `src/workflow/mysql.rs`、`src/private.rs` 建表,与组织协同同源。 遗留结构以 `tests/fixtures/legacy_goinone_ent_schema.mysql.sql`(由上述 Ent 生成物逐表逐列转写,12 表 177 列)为基准;`tests/legacy_schema_takeover_contract.rs` 校验该 fixture 与 Ent 生成物**不漂移**,并在真实结构上跑通接管。用例经 `OIP_MYSQL_URL` 门控(未设置即跳过),**不在 `#[ignore]` 之列**。 `events.mode` 单独控制**事件总线**:`memory` / `nats`(NATS JetStream)。`store.mode=postgres` 时审计、锁、工作流、同步游标、第三方绑定与发件箱共用 `store.database_url`;`OIP_BOOK_ORG_STORE` 单独覆盖**共表面**(组织协同 / 办事流程 / 私有数据)的存储模式,取值 `mysql` 即接管遗留关系核库。 各 store 的建表批以事务级咨询锁串行化(`src/storage.rs::ddl_batch`):`CREATE ... IF NOT EXISTS` 的幂等只对顺序执行成立,并发执行会在系统目录唯一约束上冲突,故多副本同时启动的建表互斥在 DDL 层完成,不依赖启动期退避重试兜底。 ### 遗留数据导入与键回填 遗留行与 OIP 行同表共存但**键列分属两套标识空间**,未回填的行对 OIP 不可见。`src/migrate.rs` 承担回填: | 步骤 | 接口 | 说明 | | --- | --- | --- | | 建表 | 各 store 的 `connect` | 幂等;对已存在的遗留表补列、补默认值、补软删除列 | | 登记键映射 | `stage_keys` | 迁移适配层把「遗留内码 → OIP 标识」写入 `oip_legacy_key_map`(主体类别的值即 DID,取自遗留链标识 `chainId`) | | 回填 | `backfill_legacy_keys` | 幂等回填 `oip_*` 键列并派生关系行(团队成员 / 团队身份 / 身份证明 / 职权集 / 私有属性);第二轮执行为空转 | 回填只采信**可确定推导**者:软删行不回填、成员仅取生效状态、`oip_type_name` 仅采信已是 OIP 字面量的取值、受理人与遗留状态编码不猜测、重复关联行按最小 `id` 去重。规则与已知边界见 [../OIP/docs/OIP-Book-Design.md](../OIP/docs/OIP-Book-Design.md) §6.3.4 / §6.3.5。 生产组合为 `store.mode=postgres`(关系库权威源,事务 + 行版本保障「线性一致、正确性优先」)+ `events.mode=nats`;`mongo` 承载存量兼容;`memory` 组合在启动时输出 ERROR 级警告,且重启即丢状态。 ### 多副本(集群)部署 `store.mode=postgres` 下 `OIP-Book` 可多副本运行(同一 `Service` 后 N 个实例),副本间经由**共享库**交换那些必须全局一致的状态。`memory` / `mongo` 不具备下表语义,仅适用于单实例。 | 状态 | 共享落点 | 多副本下的语义 | | --- | --- | --- | | nonce 原子消费 | `oip_nonce`(主键 `(ns_key, nonce)`) | 防重放**跨副本成立**:A 消费过的 nonce 在 B 提交被判重放 | | 全局断路器 | `oip_breaker`(单行 `id='global'`) | 任一副本激活后,**全部副本**经 `guard()` 拒绝写操作(fail-closed:共享存储不可达亦拒绝写) | | 限流 | `oip_ratelimit`(固定窗口计数) | 服务级配额全集群共同消费(进程内实现会被放大 N 倍);窗口内计数的原子 upsert 保证跨副本一致 | | 数据分享记录 | `oip_share` | A 副本签发的 `share_id` 在 B 副本可加载(“写后读”事实) | | 发件箱认领 | `oip_outbox`(`FOR UPDATE SKIP LOCKED`) | 同一批 pending 只被一个副本认领,避免重复发布与 `attempts` 多倍推进;投递仍为至少一次,下游按 `event.id` 去重 | **后台维护选主**:VET 过期扫描与 Exchange 锁超时回滚在每个副本都会启动,但仅**持有会话级 `pg_try_advisory_lock` 的 leader** 真正执行(未持锁者跳过当轮)。锁随 leader 连接关闭自动释放,另一副本下一轮接管——无需额外选主组件。 **优雅停机(滚动更新)**:收到 `SIGINT` / `SIGTERM` 后进入**退流**——`/readyz` 立即返回 `503 draining`,编排层/LB 摘除本副本;等待 `OIP_BOOK_PRE_STOP_SECS`(默认 5s)后放行 axum 优雅停机排空在途请求。故副本就绪门控 MUST 指向 `/readyz`,且 `terminationGracePeriodSeconds` 大于退流窗口。 **就绪探针**:`/readyz` 在「退流中 / 断路器打开 / PG 池连接全部断开」任一情形返回 `503`;`/healthz` 仅反映进程存活。 ## API 端点 `src/router.rs` 的 `build()` 为全部端点的单一装配点。 | 方法 | 路径 | 说明 | | --- | --- | --- | | POST | `/oip/v1/auth/login` | 登录态签发:DID Proof 与账号密码两形态**互斥**(`{did, proof}` 或 `{account, password}`) | | POST | `/oip/v1/auth/register` | 账号注册:创建主体(`type_name=person`)+ 登记凭据,**不签发会话**;账号重复返回 `OIP_4094` | | POST | `/oip/v1/auth/password` | 更改密码:校验 `old_password` 后写入新哈希;原密码错返回 `OIP_4001` | | POST | `/oip/v1/auth/password/disable` | **用户自主**停用账号密码登录(需原口令);只改开关、保留哈希,可随时重新启用 | | POST | `/oip/v1/auth/password/enable` | 重新启用账号密码登录(需原口令,凭据未被删除) | | POST | `/oip/v1/auth/assertion/exchange` | 外部断言 → 凭证:按绑定键四元组查**既有绑定**,命中才对绑定 DID 签发;不创建主体、不写绑定 | | POST | `/oip/v1/auth/refresh` | refresh 令牌轮换 | | POST | `/oip/v1/auth/logout` | 注销会话 | | POST | `/oip/v1/auth/challenge` | 生成登录挑战 | | GET | `/oip/v1/otp/private-key` | 查询 OTP 私钥(§5A.9 `QueryOtpPrivateKey`):`?subject_id=` 返回 TOTP 共享密钥 base32 原文;未登记返回 `OIP_4041` | | POST | `/oip/v1/vet/issue` | 签发 VET(Ed25519 签名);可选**客体绑定** `object{type,id}` 与**参数边界** `constraints.parameters`,提供时 MUST 分别附 `object_signature` / `constraints_signature`(由签发方独立背书) | | POST | `/oip/v1/vet/verify` | PDP 分阶段验证(Stage 4 复查边界背书、Stage 4.5 复查客体背书后,再判 `resource_id == object.id`) | | POST | `/oip/v1/vet/revoke` | 撤销 VET(级联消亡) | | GET | `/oip/v1/vet/:vet_id` | 查询 VET 详情 | | GET | `/oip/v1/vet/:vet_id/delegation-chain` | 委托链查询 | | GET | `/oip/v1/vet/by-object` | 按**客体**查该客体上调用方可主张的可流转 VET(只读;只返回 `holder == caller`,最小投影;缺 `caller_did` 拒绝) | | POST | `/oip/v1/vet/bind` / `/oip/v1/vet/unbind` | 第三方账号绑定 / 解绑(§5A.10) | | GET | `/oip/v1/vet/bindings/:target_did` | 查询主体的绑定列表 | | GET | `/oip/v1/transfer/prepare` | 交割准备(只读):回 `signing_payload` 与边界/客体两条 `*_signing_payload` 待签原文,供源方签名后交 `commit` | | POST | `/oip/v1/transfer/override` | Override 强制转移(HSM 监管签名);**控制权转移** ⇒ 同客体其余可流转 VET 同事务一并撤销 | | POST | `/oip/v1/transfer/workflow` | Workflow 转移创建(含审批流) | | POST | `/oip/v1/transfer/workflow/approve` | 工作流审批(审批链走完即完成转移 ⇒ 同上级联) | | GET | `/oip/v1/transfer/workflow/pending` | 查"等我签"的流程(只读;已签过的不再列出) | | GET | `/oip/v1/transfer/workflow/{workflow_id}` | 查一条流程的状态(只读;仅链条相关方可看,非相关方与不存在同码) | | POST | `/oip/v1/transfer/lock` | Exchange 锁定(超时回滚) | | POST | `/oip/v1/transfer/commit` | Exchange 提交(发新撤原 ⇒ 同上级联) | | POST | `/oip/v1/transfer/collaborate` | Collaboration 协同转移(**不转移所有权**,不触发级联) | | POST | `/oip/v1/regulator/emergency-break` | 监管紧急断路 | | POST | `/oip/v1/regulator/release-break` | 解除断路 | | GET | `/oip/v1/regulator/break-status` | 断路状态查询 | | GET | `/oip/v1/audit/trace` | 审计追溯 | | GET | `/oip/v1/audit/merkle-proof` | Merkle 包含性证明 | | POST | `/oip/v1/audit/verify-integrity` | 审计完整性验证 | | GET | `/oip/v1/audit/events` | 审计事件流 | | POST | `/oip/v1/target` | 创建目标(Target 即 DID) | | POST | `/oip/v1/target/query` | 按 ID 查询目标 | | GET | `/oip/v1/target/search` | 模糊搜索目标 | | DELETE | `/oip/v1/target/:id` | 删除目标 | | PUT | `/oip/v1/target/:id` | 更新目标 | | POST | `/oip/v1/identity` | 创建身份(VET) | | POST | `/oip/v1/identity/give` | 分配身份(VET) | | POST | `/oip/v1/identity/remove` | 移除身份(VET) | | POST | `/oip/v1/authority` | 创建职权(VET) | | GET | `/oip/v1/authority/tree` | 职权树 | | POST | `/oip/v1/relations/create` | 建立关系(VET) | | POST | `/oip/v1/relations/validate` | 关系举证验证 | | POST | `/oip/v1/team` | 团队创建 | | POST | `/oip/v1/team/join` | 加入团队 | | POST | `/oip/v1/team/leave` | 退出团队 | | POST | `/oip/v1/work/define` | 创建办事定义 | | GET | `/oip/v1/work/define/:id` | 查询办事定义 | | POST | `/oip/v1/work/define/:id/switch` | 切换定义版本 | | POST | `/oip/v1/work/instance` | 创建办事实例 | | POST | `/oip/v1/work/task/approval` | 审批任务 | | POST | `/oip/v1/work/gateway` | 创建工作网关 | | GET | `/oip/v1/work/gateways` | 查询工作网关 | | POST | `/oip/v1/sync/snapshot` | 生成同步快照(§5B.7) | | POST | `/oip/v1/sync/cursor/:node_id` | 查询下游节点同步游标 | | POST | `/oip/v1/sync/cursor/:node_id/advance` | 上报已消费游标 | | POST | `/oip/v1/system/data-share` | 签发数据分享记录 / 数据许可令牌(§5B.5) | | POST | `/oip/v1/system/load-share` | 加载数据分享记录 | | GET | `/healthz` | 存活探针 | | GET | `/readyz` | 就绪探针 | ## 错误码 `src/error.rs` 的 `OipErrorCode::as_str()` 定义对外错误码,`http_status()` 定义 HTTP 映射。 | 错误码 | HTTP | 含义 | | --- | --- | --- | | `OIP_4001` | 400 | 参数非法 | | `OIP_4002` | 400 | 结构校验失败 | | `OIP_4003` | 400 | 委托超出范围 | | `OIP_4005` | 400 | 委托深度超限 | | `OIP_4006` | 400 | 市场挂牌非法 | | `OIP_4011` | 401 | 举证/令牌校验失败(含入站内部令牌门) | | `OIP_4012` | 401 | VET 已过期 | | `OIP_4013` | 401 | VET 已撤销 | | `OIP_4031` | 403 | PDP 拒绝(布尔盲化,不含阶段原因) | | `OIP_4033` | 403 | 合规拒绝 | | `OIP_4034` | 403 | 缺失清单 VET | | `OIP_4035` | 403 | 缺失审计 VET | | `OIP_4041` | 404 | 资源不存在 | | `OIP_4091` | 409 | VET 已锁定 | | `OIP_4092` | 409 | 工作流实例占用 | | `OIP_4093` | 409 | Override 冲突 | | `OIP_4094` | 409 | 状态冲突 | | `OIP_4291` | 429 | 限流 | | `OIP_5001` | 500 | 服务内部错误(通用消息) | | `OIP_5031` | 503 | 服务不可用(含断路期间) | ## 目录结构 ```text OIP-Book/ ├── Cargo.toml # 工程清单(edition 2024,license MIT) ├── config.example.yaml # 配置模板(config.yaml 已 gitignore) ├── docker-compose.yml # 本地依赖编排 ├── deploy/ # 集群部署示例 + 启动脚本(start-cluster.sh;K8s 清单 + Secret 模板;单机多副本 compose + nginx 反代) ├── deny.toml # cargo-deny 依赖策略 ├── AGENTS.md # 协作约定 ├── README.md / README.en.md ├── src/ │ ├── main.rs # 二进制入口(薄壳:加载配置 → 启动 HTTP + 维护任务 + 发件箱中继) │ ├── lib.rs # 库入口(模块声明,导出 Config / AppState) │ ├── breaker.rs # 全局断路器状态机(Open/HalfOpen/Closed) │ ├── clock.rs # 时间抽象(SystemClock / ManualClock) │ ├── compliance.rs # 合规引擎(制裁清单 + GDPR) │ ├── config.rs # 配置加载与校验 │ ├── credentials.rs # 账号密码凭据(trait + 内存实现;argon2 PHC / TOTP base32) │ ├── crypto.rs # Ed25519 签发/验签 / HSM 签名 / canonical payload │ ├── did.rs # DID 注册与状态管理 │ ├── effects.rs # 权威源操作的副作用与处置登记(可逆 / 可补偿 / 不可逆) │ ├── error.rs # OIP 错误码与统一错误 │ ├── events.rs # 审计事件(内存 / NATS JetStream)+ 主题注册表 │ ├── handlers/ # HTTP 处理器(audit / auth / binding / health / org / regulator / sync / system / transfer / vet / workflow) │ ├── lifecycle.rs # VET 状态机与转移法则 │ ├── merkle.rs # Merkle 审计树 │ ├── migrate.rs # 遗留关系核 → OIP 的键回填与数据导入(oip_legacy_key_map + 幂等回填) │ ├── mysql.rs # MySQL 共表面公共设施(连接 / DDL 会话锁 / id 分配 / 方言取值) │ ├── org/ # 组织协同承接(types / store / postgres / mysql) │ ├── outbox.rs # 发件箱(原子双写 + 异步补投,死信不删除) │ ├── pdp.rs # PDP 分阶段校验管线 + 布尔盲化 │ ├── private.rs # 私有属性值(遗留 tb_private 承接面;值经 Debug 脱敏) │ ├── protocol_gen.rs # 协议面规范表(由 gen_protocol.py 生成) │ ├── ratelimit.rs # 令牌桶限流 │ ├── response.rs # 统一 OperateResult 响应 │ ├── router.rs # 路由装配 + 内部服务令牌门 + 路由层集成测试 │ ├── runtime.rs # 服务运行时装配(后台过期扫描、锁超时回滚) │ ├── share.rs # 数据分享记录与数据许可令牌 │ ├── state.rs # 全局状态与存储/事件后端选型 │ ├── storage.rs # 存储抽象(PostgreSQL / MongoDB / 内存) │ ├── sync.rs # 节点间数据同步(SyncSnapshot 生成 + 同步游标存储) │ ├── topics_gen.rs # NATS 主题规范表(由 gen_protocol.py 生成) │ ├── transfer.rs # 四大转移法则 │ ├── types.rs # VET / DID / 委托策略 / 事件类型 │ └── workflow/ # 办事流程管理(mod + store + mysql) └── tests/ ├── fixtures/ # 遗留关系核 Ent 结构基准(MySQL 方言) ├── auth_issuance_contract.rs # 认证签发契约集成测试 ├── credential_revocation_contract.rs # 凭据吊销契约 ├── identity_grant_contract.rs # 身份授予契约 ├── otp_binding_contract.rs # 动态口令绑定契约 ├── target_list_contract.rs # 主体列表契约 ├── work_todo_contract.rs # 办事待办契约 └── legacy_schema_takeover_contract.rs # 遗留结构漂移门禁 + 接管契约(OIP_MYSQL_URL 门控) ``` ## 测试 | 层级 | 位置 | 说明 | | --- | --- | --- | | 单元测试 | 各模块 `#[cfg(test)]` | 状态机转移、PDP 分阶段、Merkle 证明、限流、断路器、存储抽象 | | 路由集成测试 | `src/router.rs` 的 `#[cfg(test)]` | 经 `tower::ServiceExt::oneshot` 发起真实 HTTP 请求断言 | | 契约集成测试 | `tests/auth_issuance_contract.rs` | 认证签发面契约(26 个用例,含账号注册 / 账号密码登录 / 更改密码 / 断言兑换 / OTP 私钥) | | 外部依赖用例 | `#[ignore]` + `OIP_POSTGRES_URL` / `OIP_REDIS_URL` | 需真实 PostgreSQL / Redis,未设置时打印 `skip` 并返回 | | 遗留结构接管契约 | `tests/legacy_schema_takeover_contract.rs` | 含「fixture ↔ Ent 生成物」漂移门禁与真实 Ent 结构上的接管契约;经 `OIP_MYSQL_URL` 门控(未设置为跳过,**非 `#[ignore]`**) | ## 安全基线 - PDP 布尔盲化:所有拒绝路径返回相同的 `OIP_4031` 响应体,阶段原因只进内部 `detail`,防止信息泄露 - Override 转移与紧急断路(`force_level` ≥ 50 / 250)必须携带 HSM 签名的监管指令;断路器触发后全局拒绝写操作 - 500 类错误对外返回通用信息,完整异常只进内部日志 - 认证、VET 签发/验证/撤销、Override 转移与数据分享端点受令牌桶限流(配额见 `src/ratelimit.rs`);nonce 以 `(key, nonce)` 二元组原子消费防重放(Redis / 内存双实现) - 内部服务令牌门:配置环境变量 `OIP_BOOK_INTERNAL_TOKEN`(非空即启用)后,除 `/healthz`、`/readyz` 外的全部端点要求 `Authorization: Bearer `,校验采用常量时间比较,失败返回 `OIP_4011` - 配置仅经 `config.yaml` 注入(`.gitignore` 排除,不入库),凭据不得硬编码 - 账号密码凭据:密码仅以 argon2id PHC 字符串存储(`Argon2::default()`:m=19 MiB / t=2 / p=1),明文与哈希均经手写 `Debug` 脱敏;`account` 唯一且与 DID 一一对应 - 账号枚举防护:账号密码登录与更改密码在「账号不存在」与「密码错误」时返回逐字相同的对外错误(登录 `OIP_4011`、改密 `OIP_4001`),且账号不存在分支执行同等的 argon2 校验以削弱时间侧信道 - 外部断言兑换(`auth/assertion/exchange`)只读既有绑定事实:断言 MUST NOT 创建主体、MUST NOT 写入绑定,断言字段 MUST NOT 被当作 DID(签发恒用绑定事实里的 `target_did`) - DID Proof 登录的严格性不放宽:主体须已注册且 active、须已登记 Auth Key、Proof 必填且验签通过;`account_password` 桥接字段已移除 ## 文档 - 设计文档(OIP 规范仓库):[OIP-Book-Design.md](../OIP/docs/OIP-Book-Design.md) - 去中心化设计(OIP 规范仓库):[OIP-Book-Decentralization-Design.md](../OIP/docs/OIP-Book-Decentralization-Design.md) - 协议规范(OIP 规范仓库):[OIP-Protocol.md](../OIP/docs/OIP-Protocol.md) - 测试策略(OIP 规范仓库):[OIP-Test-Strategy.md](../OIP/docs/OIP-Test-Strategy.md) - 协议面生成脚本(OIP 规范仓库):[gen_protocol.py](../OIP/tools/gen_protocol.py) - 开发约定:[AGENTS.md](AGENTS.md) - License: MIT