# NovaOSExecution **Repository Path**: anchorstack/nova-osexecution ## Basic Information - **Project Name**: NovaOSExecution - **Description**: AnchorProtocol协议的实现示例,也是一个完整元数据驱动的数据 CRUD 平台,内置事务、租户隔离、乐观锁、逻辑删除、幂等、审计、dry-run 等横切能力,并提供自签JWT 身份体系、默认拒绝的功能/行级权限模型、认证与执行端点限流,以及一个 SQLAdmin后台用于维护元数据、用户与权限。 - **Primary Language**: Unknown - **License**: AGPL-3.0 - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-10-14 - **Last Updated**: 2026-10-15 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # NovaOS执行层服务 一个**元数据驱动的数据 CRUD 平台**:以统一的[AnchorJSON 协议](https://gitee.com/anchorstack/anchor-protocol)对外提供数据读写能力, 表与字段的开放范围、行级可见范围全部由运行期元数据决定——运营侧在后台维护字段声明 即相当于开通协议访问,**新增业务表无需改代码**。 内置事务、租户隔离、乐观锁、逻辑删除、幂等、审计、dry-run 等横切能力,并提供自签 JWT 身份体系、默认拒绝的功能/行级权限模型、认证与执行端点限流,以及一个 SQLAdmin 后台用于维护元数据、用户与权限。 --- ## 目录 - [核心特性](#核心特性) - [架构分层](#架构分层) - [快速开始](#快速开始) - [配置项](#配置项) - [API 一览](#api-一览) - [协议调用示例](#协议调用示例) - [权限模型](#权限模型) - [元数据驱动](#元数据驱动) - [测试](#测试) - [已知边界](#已知边界) --- ## 核心特性 - **协议执行层**:`query` / `create` / `update` / `delete` / `batch` 五个 Standard 动作 + 能力发现(`GET /api/v1/capabilities`)。 - **横切能力**:请求级事务、租户强制隔离、乐观锁(`version`)、逻辑删除(`deleted_at`)、 幂等键、审计留痕、dry-run 预演。 - **元数据驱动**:字段声明 + 结构定义(1:1 同表单)、按元数据生成物理表(DDL 管理页)、 批量导入、公式与校验规则子表。 - **权限模型**:功能权限(table / field,默认拒绝)+ 行级范围(`all` / `self`,经 `sys_row_owners` 归属映射实现);联查时逐表独立判定。 - **身份与凭据**:自签 JWT(支持密钥平滑轮换)、bcrypt 口令、账户级超级管理员、自助改密 (改密即失效旧 Token)。 - **限流与审计**:令牌桶限流(认证端点防爆破、执行端点可用性),全部失败关闭、默认拒绝。 --- ## 架构分层 ``` core ← execution ← api ↑_______________| ``` - **core/** —— AnchorJSON 协议的数据结构与错误码,**只依赖 pydantic**,是中立底座 (`api` 与 `execution` 都依赖它,因此它必须保持在两者之下以避免循环依赖)。 - **execution/** —— 执行层实现:把已校验的指令翻译成 SQL 并落库(事务 / 隔离 / 锁 / 幂等 / 审计)。不依赖 HTTP 框架。 - **api/** —— HTTP 表面:路由、Header 解析、协议化异常渲染、限流接线。 - **auth/** —— 身份与签发纯逻辑:JWT 密钥、口令哈希、Token 签发与吊销、用户。 - **admin/** —— SQLAdmin 后台:元数据、用户、权限规则、DDL 管理。 - **models/** —— 系统表 ORM 模型(`sys_` / `meta_` 前缀)。 - **templates/** —— 覆盖 sqladmin 内置模板的页面模板。 ``` core/ 协议数据结构与错误码(AnchorJSON v0.1) execution/ 执行层:registry / context / executor / acl / locks / audit ... api/ HTTP 表面:router_protocol / router_auth / router_manage / handlers_* auth/ 身份与签发:keys / passwords / tokens / users admin/ 后台视图:business_fields / users / acl_rules / ddl / bulk_import models/ 系统表 ORM 模型(元数据、权限、归属、审计、幂等、用户) templates/ 后台自定义页面模板 test/ pytest 用例(协议契约、执行层、权限、限流、DDL ...) ``` ### 请求处理链路 ``` HTTP 请求 └─ api(解析 Header → 认证验签 → 限流 → 动作是否已注册 → 动作必填字段校验) └─ execution.executor(开事务 → 幂等认领 → handler.execute → 回填幂等 → 事务外审计) └─ execution.context(单次原语调用 → SQL) └─ 数据库 ``` `ActionHandler` 是执行器的**一次运行参数**而非另一层抽象:执行器不按 action 写分支, 动作差异全在 handler 里;handler 也不碰事务、租户注入、乐观锁等横切能力——那些由执行 层统一提供。 --- ## 快速开始 ### 环境要求 - Python `>= 3.12` - [uv](https://docs.astral.sh/uv/)(推荐,仓库含 `uv.lock`) ### 安装 ```bash uv sync # 安装依赖(含 dev 组,pyproject 已声明) ``` ### 配置 JWT 密钥**必须**通过环境变量注入,缺失或短于 32 字节会在**启动阶段直接失败** (fail fast): ```bash # 生成密钥(至少 32 字节) export ANCHOR_JWT_SECRET="$(openssl rand -hex 32)" ``` 开发时也可在项目根放一个 `.env`(已在 `.gitignore` 中,绝不入库),进程环境变量优先: ```dotenv ANCHOR_JWT_SECRET=<用 openssl rand -hex 32 生成> # 可选:历史密钥,逗号分隔,用于轮换期间继续验签存量 Token # ANCHOR_JWT_PREVIOUS_SECRETS= ``` ### 启动服务 ```bash uv run python main.py # 监听 0.0.0.0:8000 ``` 启动后可访问: | 入口 | 地址 | |---|---| | 协议接口 | `http://127.0.0.1:8000/api/v1/execute` | | 能力发现 | `http://127.0.0.1:8000/api/v1/capabilities` | | 认证签发 | `http://127.0.0.1:8000/api/v1/auth/token` | | 数据管理 | `http://127.0.0.1:8000/api/v1/manage/{target}` | | 后台管理 | `http://127.0.0.1:8000/admin` | > 启动顺序在 [main.py](file:///Users/lianallien/Documents/NovaOS_execution/main.py) 中有硬依赖: > `setup_admin` → `setup_auth`(装载密钥)→ `init_db`(建表)→ `setup_api`(注入执行器与认证器) > → `setup_manage`(复用认证器),不可调换。 ### 一键演示(推荐首次体验) `setup_crm.py` 会在**独立 SQLite 库**(默认 `crm.db`,与开发库隔离)里装配一套可跑的 CRM 元数据骨架:写元数据 → 生成物理表 → 授权 → 造演示数据 → 回填归属映射,随后启动 服务。演示入口的 JWT 密钥在运行时随机生成,因此**不依赖 `.env`**。 ```bash uv run python setup_crm.py # 装配后启动服务(127.0.0.1:8000) uv run python setup_crm.py --verify # 装配 + 端到端验证后退出 uv run python setup_crm.py --no-serve # 只装配,不起服务(供脚本 / CI 复用) uv run python setup_crm.py --port 9000 # 换监听端口 ``` 运行后会打印随机演示口令与各身份 id。演示内含四张业务表 (`crm_accounts` / `crm_contacts` / `crm_opportunities` / `crm_activities`)与四个身份: | 用户名 | 角色 | 权限 | |---|---|---| | `alice` / `bob` | `sales` | 四张表 read + write,行级 `self`(仅见挂在自己名下的行) | | `carol` | `sales_manager` | 四张表 read + write,行级 `all` | | `root` | `admin`(**账户级超级管理员**) | 走数据管理接口 `/api/v1/manage` | --- ## 配置项 | 环境变量 | 默认值 | 说明 | |---|---|---| | `ANCHOR_JWT_SECRET` | —(**必填**) | JWT 主密钥,HS256,至少 32 字节 | | `ANCHOR_JWT_PREVIOUS_SECRETS` | 空 | 轮换期间仍可验签的历史密钥,逗号分隔 | | `ANCHOR_DATABASE_URL` | `sqlite:///example.db` | 数据库连接串 | | `ANCHOR_PASSWORD_MIN_LENGTH` | `10` | 口令最小长度 | | `ANCHOR_PASSWORD_REQUIRE_LETTER` | `true` | 口令须含字母 | | `ANCHOR_PASSWORD_REQUIRE_DIGIT` | `true` | 口令须含数字 | | `ANCHOR_PASSWORD_REQUIRE_SYMBOL` | `false` | 口令须含符号 | | `ANCHOR_RATE_LIMIT_ENABLED` | `true` | 全局开关限流 | | `ANCHOR_RATE_LIMIT_{RULE}_PER_MINUTE` | 见下 | 逐规则速率(每分钟) | | `ANCHOR_RATE_LIMIT_{RULE}_BURST` | 见下 | 逐规则突发容量 | | `ANCHOR_MAX_AFFECTED_ROWS` | `1000` | 单次 update/delete 允许影响的最大行数 | | `ANCHOR_HARD_DELETE_ROLES` | 空 | 允许物理删除的角色白名单(空 = 任何人都不允许) | 限流规则默认值(`{RULE}` 取 `AUTH` / `AUTH_FLOOD` / `PASSWORD` / `EXECUTE` / `MANAGE`): | 规则 | 键 | 默认速率 / 突发 | 定位 | |---|---|---|---| | `auth` | tenant + username + ip | 5/分钟,burst 10 | 登录防爆破(主力) | | `auth_flood` | ip | 600/分钟,burst 1200 | 仅 DDoS 兜底 | | `password` | tenant + user_id | 5/分钟,burst 5 | 改密防爆破 | | `execute` | tenant + user_id | 300/分钟,burst 600 | 可用性 | | `manage` | tenant + user_id | 120/分钟,burst 240 | 可用性 | --- ## API 一览 ### 协议 | 方法 | 路径 | 说明 | |---|---|---| | `POST` | `/api/v1/execute` | 执行一条 AnchorJSON 指令(返回协议信封) | | `GET` | `/api/v1/capabilities` | 能力发现:列出全部已注册的 action | ### 认证 | 方法 | 路径 | 说明 | |---|---|---| | `POST` | `/api/v1/auth/token` | 用租户 + 用户名 + 口令换取访问 Token | | `POST` | `/api/v1/auth/password` | 修改当前账户口令(需旧口令,改密即吊销既有 Token) | > 认证端点成功返回常见 OAuth2 / REST 结构(不套协议信封);**失败响应仍走协议错误信封**, > 保持一致的错误码与 `trace_id`。 ### 数据管理(仅账户级超级管理员) | 方法 | 路径 | 说明 | |---|---|---| | `GET` | `/api/v1/manage/{target}` | 分页查看租户内某业务表的全部记录 | | `GET` | `/api/v1/manage/{target}/{row_id}` | 按主键查看单条记录 | | `GET` | `/api/v1/manage/{target}/{row_id}/owners` | 查看某条记录的属主集合 | | `PUT` | `/api/v1/manage/{target}/{row_id}/owners` | 替换某条记录的属主集合(调整归属) | ### 请求头(协议 3.3:元信息走 Header,不进请求体) | Header | 必填 | 说明 | |---|---|---| | `Authorization` | 是 | `Bearer {token}` | | `X-Trace-Id` | 否 | 全链路追踪 ID,缺省由服务端生成 | | `X-Idempotency-Key` | 否 | 幂等键:同 Key 同请求回放首次结果 | | `X-Dry-Run` | 否 | `true` 时只校验预演、不落库 | | `X-Timeout-Ms` | 否 | 执行超时(毫秒) | | `X-Priority` | 否 | `low` / `normal` / `high` | | `Accept-Language` / `User-Agent` | 否 | 语言环境与客户端标识 | ### 错误码 响应信封的 `code` 与 HTTP 状态码同值,`0` 表示成功: | 码 | 含义 | |---|---| | `400` | 请求参数错误:必填缺失、类型不匹配、无条件更新 | | `401` | 未认证:Token 过期或无效 | | `403` | 无权限:功能/行级权限校验失败 | | `404` | 资源不存在:目标实体或记录不存在(越权行命中 0 行亦返回 404,不泄露行是否存在) | | `409` | 冲突:乐观锁版本冲突 | | `422` | 校验失败:未注册的 action、契约校验不通过 | | `429` | 请求过于频繁:限流触发(带 `Retry-After`) | | `500` | 服务器内部错误 | | `503` | 服务不可用(执行层未接入 / 动作无执行逻辑) | --- ## 协议调用示例 **1. 登录取 Token** ```bash curl -s -X POST http://127.0.0.1:8000/api/v1/auth/token \ -H 'Content-Type: application/json' \ -d '{"tenant_id":"acme","username":"alice","password":"<演示口令>"}' ``` **2. 条件查询(含联查)** ```bash curl -s -X POST http://127.0.0.1:8000/api/v1/execute \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $TOKEN" \ -d '{ "anchor_version": "0.1", "action": "query", "target": "crm_accounts", "payload": { "fields": ["crm_accounts.id", "crm_contacts.email"], "where": {"level": "key"}, "order_by": ["-created_at"], "load_mode": "pagination", "page": 1, "page_size": 20, "joins": [ {"entity": "crm_contacts", "type": "inner", "on": "crm_accounts.id = crm_contacts.account_id"} ] } }' ``` **3. 创建记录** ```bash curl -s -X POST http://127.0.0.1:8000/api/v1/execute \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $TOKEN" \ -H 'X-Idempotency-Key: create-acc-001' \ -d '{ "anchor_version": "0.1", "action": "create", "target": "crm_accounts", "payload": {"data": {"id": "acc-001", "name": "示例客户", "level": "normal"}} }' ``` **4. 更新记录(乐观锁)** —— `version` 必填,版本不匹配返回 409 ```bash curl -s -X POST http://127.0.0.1:8000/api/v1/execute \ -H 'Content-Type: application/json' \ -H "Authorization: Bearer $TOKEN" \ -d '{ "anchor_version": "0.1", "action": "update", "target": "crm_accounts", "payload": {"where": {"id": "acc-001"}, "version": 1, "data": {"phone": "010-0000"}} }' ``` 支持的 `where` 操作符:`eq`(默认)/ `ne` / `in` / `nin` / `between` / `isnull` / `contains` / `startswith` / `endswith` / `like` / `gt` / `gte` / `lt` / `lte`, 逻辑组合支持 `and` / `or` / `not`。字段赋值算子:`$field` / `$inc` / `$mul` / `$func`。 --- ## 权限模型 **默认拒绝**(fail-closed)——未显式授权即不可访问。 - **功能权限**(`meta_acl_rules`,`resource_type=table`):按表 + 角色授予 `read` / `write` (`write` 蕴含 `read`);字段级白名单来自 `meta_business_fields` 声明。 - **行级范围**(`meta_acl_rules`,`resource_type=row`):`scope` 取 `all`(全部)或 `self`(仅本人名下)。同一张表命中多条规则时取**范围最宽**者。 - **归属映射**(`sys_row_owners`):结构为 `(tenant_id, table_name, row_key, user_id)`, 允许一行多属主。`self` 范围即「该行是否在映射里挂给当前用户」。 - **租户隔离**:`tenant_id` 取自 Token,执行层强制注入,**绝不采信请求体** (协议 4.6「不信任上游传入」)。取其他租户的 `where` 条件一律 403。 - **物理删除**:默认关闭,仅 `ANCHOR_HARD_DELETE_ROLES` 白名单内的角色可执行。 --- ## 元数据驱动 一张表能否被协议访问、哪些字段可见,完全由元数据决定,运行期**不需要任何 ORM 实体**: 1. 在后台「字段声明」页维护字段元数据(`meta_business_fields`)与结构定义 (`meta_table_schemas`,两者 1:1 在同表单维护)。 2. 保存时自动同步物理表(表不存在则建,存在则补列),也可在「DDL 管理」页显式 生成 / 补列 / 删除。 3. 维护权限规则(ACL 页)与行级归属。 字段声明中的**业务列**是协议侧唯一认可的字段白名单;`tenant_id` / `version` / `deleted_at` 三个系统列由执行层托管,声明进元数据会被拒绝,生成物理表时自动注入。 --- ## 测试 ```bash uv run pytest # 运行全部用例(测试库为临时文件,自动隔离) uv run pytest -q test/test_execute_endpoint.py # 单文件 ``` 测试会自动把数据库切到临时文件,不会污染开发库 `example.db`;用例间通过 fixture 清空业务、系统与元数据表并复位模块级缓存,保证互不影响。