openxiangda-skill-kit 2.0.0-alpha.19 → 2.0.0-alpha.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/docs/architecture/admin-shell-v2.md +479 -71
- package/docs/architecture/authorization-consistency-v2.md +419 -0
- package/docs/architecture/environment-configuration-kernel-v2.md +288 -0
- package/docs/architecture/implementation-roadmap.md +77 -0
- package/docs/architecture/local-development-v2.md +134 -0
- package/docs/architecture/native-configuration-projection-v2.md +488 -0
- package/docs/architecture/native-kernel-inventory-v2.md +196 -0
- package/docs/architecture/repository-and-release.md +1 -0
- package/docs/architecture/tenant-public-origin-implementation-blueprint.md +484 -0
- package/docs/architecture/tenant-public-origin-v2.md +236 -0
- package/docs/architecture/verification-orchestration-v2.md +24 -0
- package/docs/backend.md +16 -7
- package/docs/data-authz.md +27 -2
- package/docs/delivery.md +2 -2
- package/docs/design/admin-shell-dashboard-v2.png +0 -0
- package/docs/design/admin-standard-pages-v2.png +0 -0
- package/docs/frontend.md +32 -11
- package/docs/getting-started.md +20 -14
- package/docs/index.md +2 -0
- package/docs/llms.txt +7 -0
- package/docs/reference/cli.md +17 -2
- package/docs/reference/mcp.md +1 -0
- package/docs/school-contact-relations.md +117 -0
- package/docs/workflow-events.md +23 -1
- package/package.json +2 -2
- package/skills/manifest.json +6 -6
- package/skills/openxiangda-v2/SKILL.md +13 -0
- package/skills/openxiangda-v2-backend/SKILL.md +7 -1
- package/skills/openxiangda-v2-data-authz/SKILL.md +7 -1
- package/skills/openxiangda-v2-frontend/SKILL.md +39 -3
- package/skills/openxiangda-v2-workflow-events/SKILL.md +22 -5
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# OpenXiangda 2.0 环境配置内核
|
|
2
|
+
|
|
3
|
+
状态:方案已确认并按 Native 实施顺序推进。2026-08-15 已完成 E4 运行时闭环:应用身份和预发/生产原生环境成对创建、Native DeploymentRun、按 run 隔离的候选 workload、`pending -> active -> retiring -> revoked` 运行凭据、凭据与 Head CAS 同事务激活、旧 token 随 Head 变化立即失效;App Gateway 使用最长 60 秒 invocation token + 最长 30 秒 Ed25519 请求 assertion,官方 NestJS 全局 transport guard 绑定当前 Native Head 与完整 HTTP 请求,本地平台使用同构协议且拒绝重放。单体 Nest 的后台消费由短租约选出一个活动实例,应用 Secret 只允许当前 Head 身份按声明读取,terminal/cancelled/超时候选由精确 GC 回收,`alpha -> native-2` 切换必须通过全平台实例 release/capability 门禁。Native production 仍须完成后续 E5 和真实预发 E2E 后再开放。
|
|
4
|
+
|
|
5
|
+
## 1. 决策摘要
|
|
6
|
+
|
|
7
|
+
OpenXiangda 2.0 的运行语义采用三层模型:
|
|
8
|
+
|
|
9
|
+
1. **不可变定义层**:AppVersion 组合 frontend、backend、config、contracts 等不可变修订;包内角色、能力、数据策略和逻辑数据定义从 config revision 编译,contracts revision 保存跨前后端消费的资源/能力/事件/流程契约摘要,不在部署时覆盖应用级“当前配置”。
|
|
10
|
+
2. **环境选择层**:最终的 `app_runtime_environment_heads_v2` 是某个原生环境当前运行哪个 AppVersion/DeploymentRun 的唯一指针。预发和生产可以选择不同修订;晋级仍使用同一个 AppVersion,不重建制品。现有 `app_environment_heads_v2` 的 `environment_id` 外键指向 legacy `app_environments`,只作为 alpha 历史审计事实,不能原地改造成最终 Head。
|
|
11
|
+
3. **环境运行态层**:RoleMembership、manual role、业务范围 grant、RelationshipGrant、RoleSession、Workflow 实例、Event receipt、OAuth client 和 Secret 等可变状态绑定远程环境。生产运行态不能被预发部署覆盖。
|
|
12
|
+
|
|
13
|
+
2.0 只有一套远程环境身份模型:一个稳定 `tenant + appCode` 下恰好包含 `preproduction`、`production` 两个原生运行环境,每个环境有稳定 UUID 和不可变 route key。同一个 AppVersion 先部署到预发,再原样晋级生产。`local` 是开发机上的运行模式,不注册环境 UUID、不创建远程 Head/OAuth/Secret/RoleSession,也不能作为 deploy/promote 目标。现有 `app_environment_sets/app_environments` 用“不同 appType 分别代表预发和生产”的模型保留给 1.x 发布治理,不再由 2.0 CLI、AppVersion 或 native runtime 使用。
|
|
14
|
+
|
|
15
|
+
物理存储和逻辑运行契约必须分开:Data API 的物理表与新增列是应用级、单调扩展的共享基础设施,业务行继续通过 `environment_key` 隔离;当前允许访问哪些字段、capability、字段策略和 data policy,则由请求环境 Head 选择的不可变 config revision 投影决定。contracts revision 用于证明前端、后端与配置引用的是同一组稳定代码,不保存完整字段 schema。
|
|
16
|
+
|
|
17
|
+
该模型取代当前“每个环境部署都把包配置 UPSERT 到 `tenant + app` 全局活动表”的做法。不能只给旧表增加一个字段:1.x 仍依赖共享 `roles/api_permissions/business_scope_*`,直接改造会让两个内核的所有权、回滚和滚动发布继续互相牵连。2.0 建立原生投影表;1.x 原表保持原语义。
|
|
18
|
+
|
|
19
|
+
## 2. 源码证据与问题边界
|
|
20
|
+
|
|
21
|
+
| 证据 | 当前行为 | 后果 |
|
|
22
|
+
| --- | --- | --- |
|
|
23
|
+
| Deployment executor | alpha 合同仍允许 development、preproduction、production 调用同一个 `configService.activate()` | alpha development 仍能进入配置激活链路;Native v3 必须一次删除该远程通道而不是保留兼容别名 |
|
|
24
|
+
| `activateAuthorization()` | 写 `roles`、`api_permissions`、`business_scope_*` 和 `app_authz_states_v2`,唯一范围只有 `tenant + app` | 开发候选可在尚未晋级生产时改变生产授权语义 |
|
|
25
|
+
| `AppRoleSessionV2Entity` | 已包含 `environment_key` | 会话被环境隔离,但其引用的 assignment、role 和 authz state 没有隔离,边界不闭合 |
|
|
26
|
+
| `app_data_resources_v2` | schema、capability、data policy 和字段策略是 `tenant + app + code` 全局可变记录 | 开发部署可提前改变生产 Data API 的逻辑契约和授权 |
|
|
27
|
+
| Data API 物理业务表 | 行含 `environment_key`,RLS 和事务请求也绑定环境 | 数据行隔离基础已经存在,无需按环境复制物理表 |
|
|
28
|
+
| AppVersion / alpha Environment Head | AppVersion 已组合 component revision;`app_environment_heads_v2` 同时重复保存组件指针,且 `environment_id` 外键指向 legacy `app_environments` | AppVersion 组合骨架可保留并补数据库不可变约束;alpha Head 不能复用,原生 Head 只保存 AppVersion、DeploymentRun 和 CAS revision,避免两份组件指针漂移 |
|
|
29
|
+
| 两套环境实体 | legacy `app_environments` 要求每个环境绑定不同 appType;Application v2 Head 又在同一 appType 下使用环境 key,且 `environment_id` 可空 | 同一“环境”存在两种主键/晋级模型;2.0 必须引入单一原生 registry,并停止调用 legacy set |
|
|
30
|
+
| Kubernetes Runtime | workload/Service 名包含 AppVersion,候选 readiness 后才在数据库事务中更新 Head;网关读取 Head 对应 DeploymentRun 的 runtime 地址 | 候选不会在 Head 提交前承接应用网关流量;激活失败保留旧流量,但失败候选需要回收 |
|
|
31
|
+
| App API Gateway / Nest Guard | 已签发按当前 Head 和具体 HTTP 请求绑定的短期 invocation/assertion,Nest 全局 guard 先离线验签再实时消费调用身份;健康与签名回调使用各自边界 | 路径证明已闭合;下一步由 runtime lease 关闭同进程 Worker/Scheduler 的副作用激活窗口 |
|
|
32
|
+
| 候选运行凭据 | 当前 runtime credential 在候选 readiness 前已经进入可用链路;同一个 Nest 进程可以同时承载 API、Worker 和 Scheduler | 未激活 AppVersion 的启动逻辑、定时任务或 Worker 可能提前访问 Data API 或产生外部副作用,破坏原子晋级 |
|
|
33
|
+
|
|
34
|
+
问题不是单一权限缓存缺陷,而是“不可变包定义”和“环境活动投影”之间缺少统一边界。授权 A0、Data API D0 和后续 Admin A1 必须建立在同一个环境配置内核上。
|
|
35
|
+
|
|
36
|
+
## 3. 不变量与能力所有者
|
|
37
|
+
|
|
38
|
+
1. AppVersion 和所有 component revision 一经创建不可修改;规范化内容摘要由平台重算。
|
|
39
|
+
2. 每个原生环境只有一个活动 Head;用户请求只能从认证得到的 `tenant/app/environmentId` 解析 Head,不能接受业务参数覆盖。环境 key 先经 registry 精确解析并与 Principal 中的 id/key 双重核对。
|
|
40
|
+
3. 一次环境激活要么同时提交 Head、授权环境状态、Workflow/Event 活动指针和部署成功状态,要么全部不提交。
|
|
41
|
+
4. 候选 Kubernetes 工作负载通过 readiness 不代表已激活;只有数据库 Head 提交后才对网关可见。
|
|
42
|
+
5. 预发、生产的可变运行态相互独立。显式“复制角色成员/范围”是管理操作,不是环境部署的隐式副作用;本地运行态不进入远程数据库。
|
|
43
|
+
6. PostgreSQL 是活动 Head、授权版本和安全状态的事实源;Redis、浏览器缓存和 Kubernetes annotation 都不能替代它。
|
|
44
|
+
7. 物理数据 schema 只允许向前兼容、单调扩展。删除字段、改变类型或给有数据的表增加无默认值必填字段必须走独立数据迁移协议,不能混入普通 AppVersion 激活。
|
|
45
|
+
8. 1.x 继续由现有角色、权限和业务范围表负责;2.0 原生内核不得双写这些表作为长期运行方式。
|
|
46
|
+
9. 同一个 AppVersion 从预发晋级生产时使用相同 component/authz/config-contract revision;环境运行态、Secret 和 OAuth client 不随包复制。
|
|
47
|
+
10. 所有环境切换有可观察的 expected Head revision;并发部署不能以“最后提交者覆盖”伪装成两个都成功。
|
|
48
|
+
11. `environmentId` 是控制面与授权运行态的持久身份;`environmentKey` 创建后不可改,只承担 URL、日志、Kubernetes label 和现有 Data API 行隔离。需要新 key 时创建新环境并执行显式迁移,不做原地 rename。
|
|
49
|
+
12. 业务 App API 请求必须同时通过业务身份授权和平台网关路径证明;静态请求头、NodePort 可达性或集群网络位置都不是路径证明。
|
|
50
|
+
13. 候选 runtime 在 Head 提交前只能完成无副作用自检;不能取得正式应用身份,Worker/Scheduler 不能领取任务。Head 回滚或租约丢失后停止领取新任务,在途任务按幂等/lease 协议完成或接管。
|
|
51
|
+
|
|
52
|
+
能力所有者划分:
|
|
53
|
+
|
|
54
|
+
| 能力 | 唯一所有者 |
|
|
55
|
+
| --- | --- |
|
|
56
|
+
| AppVersion 与组件组合 | `ApplicationVersionV2Service` |
|
|
57
|
+
| 环境活动选择和 CAS | `ApplicationEnvironmentActivationV2Service`(从 executor 内联事务提取) |
|
|
58
|
+
| 不可变配置编译 | `ApplicationConfigurationCompilerV2Service` |
|
|
59
|
+
| 原生授权修订与环境授权状态 | `AppAuthorizationKernelV2Service` |
|
|
60
|
+
| Data API 物理 schema | `AppDataPhysicalSchemaV2Service` |
|
|
61
|
+
| Data API 逻辑契约解析 | `AppDataContractV2Service` |
|
|
62
|
+
| 候选 Runtime 创建/readiness/回收 | `ApplicationRuntimeV2Service` |
|
|
63
|
+
| 2.0 环境注册、key→id 解析与 side-effect policy | `ApplicationRuntimeEnvironmentV2Service` |
|
|
64
|
+
| 活动 runtime 解析、短时调用委托、网关断言签发与密钥轮换 | `ApplicationApiGatewayV2Service` + 平台 `ApplicationInvocationTokenService` / `GatewayAssertionKeyService` |
|
|
65
|
+
| 网关断言验证与单进程 API/Worker/Scheduler 激活门控 | 官方 `openxiangda-nest`;应用代码只注册 handler,不自行推断 Head |
|
|
66
|
+
|
|
67
|
+
Executor 只编排阶段,不直接实现授权版本 SQL、逻辑数据契约写入或 Head 选择规则。
|
|
68
|
+
|
|
69
|
+
## 4. 版本与运行态模型
|
|
70
|
+
|
|
71
|
+
### 4.0 原生环境注册
|
|
72
|
+
|
|
73
|
+
```text
|
|
74
|
+
app_runtime_environments_v2
|
|
75
|
+
id uuid,
|
|
76
|
+
tenant_id, app_type,
|
|
77
|
+
environment_key preproduction|production,
|
|
78
|
+
environment_kind preproduction|production,
|
|
79
|
+
display_name, status active|decommissioned,
|
|
80
|
+
side_effect_policy_json,
|
|
81
|
+
revision, created_by, updated_by, created_at, updated_at
|
|
82
|
+
UNIQUE (tenant_id, app_type, environment_key)
|
|
83
|
+
UNIQUE (tenant_id, app_type, environment_kind)
|
|
84
|
+
|
|
85
|
+
app_runtime_environment_heads_v2
|
|
86
|
+
environment_id uuid PRIMARY KEY -> app_runtime_environments_v2.id
|
|
87
|
+
tenant_id, app_type,
|
|
88
|
+
active_app_version_id uuid NOT NULL,
|
|
89
|
+
active_deployment_run_id uuid NOT NULL,
|
|
90
|
+
revision bigint NOT NULL,
|
|
91
|
+
activated_by, activated_at
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
- 一个 2.0 app 固定两个长期远程环境;不支持 development 远程环境、任意自定义 key、同 kind 多环境或 key rename。
|
|
95
|
+
- 新应用 provision 事务一次创建 preproduction/production;任一创建失败整笔回滚。部署请求不能隐式补环境,生产环境必须有更严格 side-effect policy 与管理员确认。
|
|
96
|
+
- `environmentId` 进入 OAuth client、Secret、RoleSession、Workflow/Event、DeploymentRun 和授权运行态;对外 DTO 同时返回 id/key/kind,但任何写请求中的 id/key 必须与认证 Principal、app 和 registry 相互匹配。
|
|
97
|
+
- 旧 `app_environment_sets/app_environments` 的跨 appType swap、attach 和 policy 接口不再出现在 openxiangda-v2 CLI/Skill。旧 CLI 和 1.x 应用保持原行为。
|
|
98
|
+
- 原生 Head 不重复保存 frontend/backend/config/contracts revision。AppVersion 是组件组合的唯一事实,`app_version_projection_bindings_v2` 是 AppVersion 到编译后配置闭包的唯一映射;读取方通过它们解析。数据库复合外键必须证明 Head、AppVersion、DeploymentRun 与 environment 属于同一个 tenant/app,不能只靠服务层比较 UUID。
|
|
99
|
+
- 现存 alpha Application v2 Head 不迁移、不预分配 native UUID,也不参与 Native Head 初始化。新的 native reference app 使用全新 app code,并通过显式环境创建命令生成 registry UUID;旧 `head.environment_id` 只留在 alpha 历史中。K4 前 alpha Head 与新应用的原生 Head 可以同时存储,但每个请求只按全局 contract generation 读取其中一套;这不是在线双读或双写。K5 保留 alpha 数据库历史,只撤销明确列出的旧 runtime principal 并删除旧 alpha K3s workload。
|
|
100
|
+
|
|
101
|
+
```mermaid
|
|
102
|
+
flowchart LR
|
|
103
|
+
Package["不可变 AppPackage"] --> Version["AppVersion"]
|
|
104
|
+
Version --> Frontend["frontend revision"]
|
|
105
|
+
Version --> Backend["backend revision"]
|
|
106
|
+
Version --> Config["config revision"]
|
|
107
|
+
Version --> Contracts["contracts revision"]
|
|
108
|
+
Head["Native Environment Head"] --> Version
|
|
109
|
+
Config --> Projection["immutable configuration projection"]
|
|
110
|
+
Contracts --> Projection
|
|
111
|
+
Projection --> AuthzRevision["deduplicated authz revision"]
|
|
112
|
+
Head --> Runtime["versioned Runtime address"]
|
|
113
|
+
Environment["Runtime Environment UUID"] --> Head
|
|
114
|
+
EnvAuthz["Environment Authz State"] --> AuthzRevision
|
|
115
|
+
EnvAuthz --> Environment
|
|
116
|
+
EnvAuthz --> Head
|
|
117
|
+
Request["authenticated environment request"] --> Head
|
|
118
|
+
Request --> EnvAuthz
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 4.1 不可变定义
|
|
122
|
+
|
|
123
|
+
- `app_component_revisions_v2` 和 `app_versions_v2` 继续保存制品摘要、组合及 provenance,但增加数据库级 UPDATE/DELETE 拒绝触发器;相同 digest 冲突时必须逐字段重读并比较,不能仅按 digest 命中后返回既有行。
|
|
124
|
+
- 配置编译器从经过字节摘要校验的 config/contracts artifact 生成只增不改的关系投影。`metadata_json.configurationBundle` 只作为 alpha 历史审计字段,不能成为原生编译输入的事实源。
|
|
125
|
+
- 授权子集单独计算 canonical digest,生成 `app_authz_revisions_v2`;相同授权声明跨多个 AppVersion 去重。
|
|
126
|
+
- `app_configuration_projections_v2` 聚合 authz、data、event、workflow 和 runtime-requirement 五类不可变领域修订;`app_version_projection_bindings_v2` 为每个原生 AppVersion 固定绑定一个完整投影。即使某领域为空也绑定一个空修订,不用 nullable 表达语义。
|
|
127
|
+
- Data API 逻辑资源定义绑定不可变 data-contract revision;contracts revision 保存编译后的 resource/capability/handler closure,而不是覆盖 `app_data_resources_v2` 的活动 JSON。
|
|
128
|
+
- Workflow/Event 的定义进入不可变领域修订;环境 Head、delivery/receipt、实例/task、暂停/恢复、轮换等运行态仍按环境保存。新 Native 应用从空状态建立这些运行态;现有 alpha 环境化表只保留历史,既不离线迁移,也不由新 config 激活器原地 UPSERT。
|
|
129
|
+
|
|
130
|
+
### 4.2 环境选择
|
|
131
|
+
|
|
132
|
+
`app_runtime_environment_heads_v2` 是新的最小活动指针,以 `environment_id` 唯一定位。它不复制组件 revision 或配置投影字段;这些事实由 AppVersion 和不可变 projection binding 提供。激活命令必须携带部署开始时观察到的 `expectedHeadRevision`:
|
|
133
|
+
|
|
134
|
+
- 首次激活要求 `expectedHeadRevision = 0`;
|
|
135
|
+
- 已有 Head 使用 `UPDATE ... WHERE revision = expectedHeadRevision RETURNING ...`;
|
|
136
|
+
- 未命中返回稳定 409,DeploymentRun 标记 conflict,不覆盖另一场已经成功的部署;
|
|
137
|
+
- 相同 DeploymentRun + 相同 AppVersion + 相同目标 Head 的超时重试返回既有成功结果,不重复推进 revision。
|
|
138
|
+
|
|
139
|
+
### 4.3 环境运行态
|
|
140
|
+
|
|
141
|
+
以下控制面和授权资源必须以 `environment_id` 外键定位,同时保留 tenant/app 复合约束用于防串联和查询计划:
|
|
142
|
+
|
|
143
|
+
- 授权环境状态、manual role、RoleMembership、delegation ceiling;
|
|
144
|
+
- scope grant source 的物化状态、有效 grant、RelationshipGrant;
|
|
145
|
+
- RoleSession;
|
|
146
|
+
- OAuth client、Secret、Event delivery/receipt、Workflow instance/task/provider binding;
|
|
147
|
+
- Data API 幂等事务和业务数据行。
|
|
148
|
+
|
|
149
|
+
最后一项暂时例外:现有动态 Data API 物理表与幂等记录以 `tenant + app + immutable environment_key` 隔离。Data API Principal 必须同时含 registry `environmentId/environmentKey`,RLS 先校验二者映射,再比较行 key。没有必要为所有大表立即回填 UUID;新控制面表不再复制这种字符串主键。
|
|
150
|
+
|
|
151
|
+
应用最高管理员是显式的应用级平台授权,可跨该应用所有环境并天然绕过业务数据范围;绕过必须出现在 explain/audit 中。普通业务角色和范围不使用跨环境通配符。
|
|
152
|
+
|
|
153
|
+
## 5. Data API:物理 schema 与逻辑契约分离
|
|
154
|
+
|
|
155
|
+
Data API 不为每个环境创建一张表。最终模型为:
|
|
156
|
+
|
|
157
|
+
| 层 | 范围 | 行为 |
|
|
158
|
+
| --- | --- | --- |
|
|
159
|
+
| physical resource | `tenant + app + resourceCode` | 稳定 physical table;保存已部署版本所需字段的兼容并集 |
|
|
160
|
+
| physical schema generation | 应用级单调整数 | 只新增 nullable 字段、索引和兼容约束;DDL 可在候选准备阶段完成 |
|
|
161
|
+
| logical resource definition | `configRevision + resourceCode` | name、可见字段、capability、data policy、field policy、状态 |
|
|
162
|
+
| runtime resolution | `tenant + app + environmentId -> native Head -> AppVersion -> projection binding -> data-contract revision` | 每次请求只使用该环境活动逻辑定义;contracts revision 校验消费代码闭合 |
|
|
163
|
+
| business row | `tenant + app + environment + id` | 由 RLS 强制环境隔离 |
|
|
164
|
+
|
|
165
|
+
候选开发版本新增 nullable 字段时,物理列可能先存在,但生产活动逻辑契约看不到也不能读写该字段。字段删除或类型改变不能通过“让生产继续用旧修订”掩盖物理兼容问题,必须先通过 expand/backfill/contract 数据迁移。
|
|
166
|
+
|
|
167
|
+
逻辑 `nullable:false` 不等于候选阶段立即添加物理 `NOT NULL`。只要任一环境 Head 仍运行不认识该字段的旧 revision,旧写入就无法提供它;因此:
|
|
168
|
+
|
|
169
|
+
1. expand 始终添加 nullable 物理列,目标环境的逻辑契约在 create/update 时执行 required 校验;
|
|
170
|
+
2. 需要数据库级 `NOT NULL` 时,AppPackage 只声明 intent,平台生成独立 schema migration plan;
|
|
171
|
+
3. 所有活动 Head 都已支持字段、历史行完成有界 backfill、验证查询为零缺失后,才以独立 contract job 设置约束;
|
|
172
|
+
4. 回滚到旧 AppVersion 不删除约束前要证明旧写路径仍能提供字段,否则回滚 preflight 拒绝。
|
|
173
|
+
|
|
174
|
+
物理 DDL 不放在最终 Head 激活事务:`AppDataPhysicalSchemaV2Service` 使用每资源 advisory lock、持久 schema-generation ledger、有限 `lock_timeout/statement_timeout` 和幂等步骤。添加索引使用 PostgreSQL 允许的并发构建路径并记录 `building|ready|failed`;失败/无效索引由精确 repair job 处理。单个 AppVersion 默认最多 20 个新增字段、10 个新增索引,超过需独立容量计划;运行中不得扫描全表计算默认值。DDL 成功但后续 runtime/Head 激活失败时,兼容的额外列/索引可以保留,旧环境逻辑契约仍不可见。
|
|
175
|
+
|
|
176
|
+
授权角色引用的 capability catalog 由 AppVersion 组合时校验:平台保留管理 capability、`authz.capabilities` 显式声明的 backend/UI capability、Data Resource 自动导出的数据/字段 capability 必须闭合;contracts revision 记录闭合后的代码集合供前后端生成类型和验包。Nest `@RequireCapability` 与 Admin route 使用生成常量,构建时校验其属于 contracts。不存在、跨应用或只被角色引用但未声明的 capability 使 AppVersion prepare 失败。
|
|
177
|
+
|
|
178
|
+
## 6. App API 路径证明与候选执行门控
|
|
179
|
+
|
|
180
|
+
### 6.1 双重授权边界
|
|
181
|
+
|
|
182
|
+
平台网关先验证调用者原始用户/OAuth2 access token,按认证上下文解析稳定 `environmentId` 和活动 Head,只能向该 Head 对应的 DeploymentRun 转发。原始 access token 到此终止,不能进入应用容器或应用日志。网关签发两个职责分离的短时工件:
|
|
183
|
+
|
|
184
|
+
1. `Authorization: Bearer <application-invocation-token>`:最长 60 秒,audience 绑定目标应用、环境和 DeploymentRun;携带最小 Principal/RoleSession 引用、源 token id 摘要和授权版本,用于 Nest 向 Data API/Workflow 等平台接口做 on-behalf-of 调用。平台消费时仍实时验证 RoleSession、membership/grant、环境 authz state 和当前 Head,不能只相信 JWT 中的 allow 结果。
|
|
185
|
+
2. `X-OpenXiangda-Gateway-Assertion`:最长 30 秒的 Ed25519 JWS,证明具体 HTTP 请求由平台网关根据当前 Head 转发。断言至少绑定:
|
|
186
|
+
|
|
187
|
+
```text
|
|
188
|
+
issuer, audience=openxiangda-app-backend,
|
|
189
|
+
tenantId, appCode, environmentId, environmentKey,
|
|
190
|
+
appVersionId, deploymentRunId,
|
|
191
|
+
method, normalizedPath, canonicalQueryDigest, bodyDigest,
|
|
192
|
+
invocationTokenDigest, roleSessionIdDigest,
|
|
193
|
+
jti, iat, nbf, exp, keyId
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
- 私钥只属于平台;runtime 只获得当前/上一把公钥或受控 JWKS,轮换期间两把公钥有界重叠。不能给每个应用复制平台签名私钥。
|
|
197
|
+
- Nest 全局 transport guard 先验证断言签名、时间、请求摘要和自身注入的 app/environment/version/run 身份,再由 AuthZ Guard 验证 invocation token、RoleSession/Application Principal 和 `@RequireCapability()`。任一层失败都 fail closed。
|
|
198
|
+
- 调用者原始 `Authorization`、RoleSession 原值和业务数据不写入断言,只写 SHA-256 digest;两个工件都不进入浏览器、日志或应用响应。`jti` 用于短窗口重放诊断;有副作用的业务操作仍必须使用应用级 idempotency key,不能把短时签名当 exactly-once。
|
|
199
|
+
- invocation token 不能换取 refresh token,不能访问别的应用/环境/run;过期、源登录会话撤销、RoleSession stale、runtime 不再是当前 Head 时拒绝。用户请求结束后应用不得持久化它。对外 OAuth2 client 仍通过平台网关调用,client access token 也不原样转发给应用。
|
|
200
|
+
- 只有 `/__platform/health`、`/__platform/ready` 和版本自检允许没有业务身份;Events 使用订阅 HMAC/receipt,Workflow provider 使用平台回调签名。其他自定义 controller 即使没有 `@RequireCapability()` 也不得绕过 transport guard;缺 capability 的服务身份请求继续拒绝。
|
|
201
|
+
- 现有 `X-OpenXiangda-Forwarded-By` 降级为诊断字段,不参与授权。NodePort、cluster DNS 或反向代理直连都因缺断言失败。
|
|
202
|
+
|
|
203
|
+
Kubernetes 仍提供第一层缩小攻击面:共享与独立档位都生成 default-deny ingress;cluster-dns 只允许标记过的平台网关 namespace,node-port 必须配置明确的 gateway source CIDR/主机防火墙规则后才允许部署。NetworkPolicy/安全组配置错误不能扩大授权,因为 Nest 的离线签名验证仍是强制边界。请求体采用流式摘要并沿用 20 MiB 上限,避免为了签名在网关和 Nest 中各无界复制一份。
|
|
204
|
+
|
|
205
|
+
### 6.2 候选 runtime 状态
|
|
206
|
+
|
|
207
|
+
runtime credential 采用 `pending -> active -> retiring -> revoked` 状态并绑定 `environmentId + appVersionId + deploymentRunId`:
|
|
208
|
+
|
|
209
|
+
1. P0 创建 pending credential 和一次可恢复的 secret;pending 只能调用版本化 readiness/activation-status 端点,token 服务不为它签发 Data API、App API、Workflow 或外部调用 scope。
|
|
210
|
+
2. P1/P2 启动完整 Nest 进程,但官方 runtime gate 只开放健康/版本自检;API 需要活动 Head 网关断言,Worker/Scheduler 尚无活动租约。应用 `OnModuleInit` 必须无业务副作用,模板测试和静态规则禁止自行启动 cron/queue consumer。
|
|
211
|
+
3. P3 在同一个数据库事务中 CAS Head、激活目标 runtime credential、推进活动 generation 并把旧 credential 标记 retiring。数据库提交后,新网关断言、invocation token 和 runtime token 才能引用该 run/version;旧 run 的 runtime token 立即不能发起新平台业务调用,在途任务由 claim lease/idempotency 重新接管。
|
|
212
|
+
4. 单体 Nest 中的 Worker/Scheduler 通过平台长轮询获得短时 runtime lease;lease 绑定当前 Head revision/run,只有活动版本可续租。失去租约后停止领取新任务;已领取任务使用既有 claim lease、幂等键和接管协议,不强杀到一半。
|
|
213
|
+
5. Event/Workflow 投递目标始终从活动 Head 解析,不向 pending/retiring runtime 派发新命令。旧版本只在有界 draining window 处理在途工作,随后撤销凭据并精确回收。
|
|
214
|
+
|
|
215
|
+
平台对自身通道提供强保证:pending runtime 无 Data API/App API/Workflow scope、不领取任务、不接收事件,也不获得 active-only Secret。应用作者仍可能在第三方库初始化时直接访问外部系统,这不是 Nest 生命周期能单独证明的边界。2.0 首期信任应用开发者,不建设域名代理、CIDR 审批或通用 egress 策略 DSL;外部系统连接由应用后端正常开发和负责。平台只保证自身发放的业务身份、租约和 Secret 在 Head 激活前不可用,避免把复杂而不完整的网络策略包装成安全保证。
|
|
216
|
+
|
|
217
|
+
## 7. 环境激活状态机
|
|
218
|
+
|
|
219
|
+
| 阶段 | 动作 | 失败语义 |
|
|
220
|
+
| --- | --- | --- |
|
|
221
|
+
| P0 immutable prepare | 校验 AppPackage,创建/复用 component、authz、config-data 投影和 pending runtime credential;校验 contracts closure;执行允许的单调 DDL | 没有活动 Head 变化;pending identity 无业务 scope;可安全重试 |
|
|
222
|
+
| P1 runtime candidate | 创建按 AppVersion 命名的 Deployment/Service/Secret | 不覆盖旧 workload;runtime gate 禁止业务入口和后台消费;失败记录可重试 |
|
|
223
|
+
| P2 readiness | 验证候选健康、版本/环境自检和受限平台握手 | 不使用正式应用身份、不产生业务副作用;未通过不进入激活事务 |
|
|
224
|
+
| P3 activation transaction | advisory lock `environmentId`;复核 tenant/app/key 与 registry;CAS Head;切 authz/data/workflow/event 指针;激活目标 credential/lease generation;记录 DeploymentRun succeeded | 任一项失败整笔回滚,旧 Head/凭据/配置/路由保持不变 |
|
|
225
|
+
| P4 post-commit | 网关按新 Head 签发断言;新 runtime 取得 lease;旧 runtime 进入 draining;清理旧缓存 namespace | 协调失败由 reconciler 重试;新请求仍只到 Head,后台未取得 lease 时保持暂停 |
|
|
226
|
+
| P5 failed candidate GC | retryable run 保留候选供同版本重试;terminal/cancelled 或超过 TTL 的候选回收 | GC 只按 run/app/version 精确删除,禁止标签宽删活动 Head workload |
|
|
227
|
+
|
|
228
|
+
当前版本化 Service 不需要替换为稳定 Service selector:应用网关已经从环境 Head 读取活动 DeploymentRun 的 runtime 地址,因此 Head 是现有流量开关。需要补的是 CAS、失败候选回收和“Head 指向的 workload 不可删除”保护。
|
|
229
|
+
|
|
230
|
+
## 8. 并发、失败与回滚
|
|
231
|
+
|
|
232
|
+
| 场景 | 必须行为 |
|
|
233
|
+
| --- | --- |
|
|
234
|
+
| 同一环境两个部署并发 | advisory lock 后用 expected Head revision;恰好一个按其预期激活,另一个 409 或在显式重新计划后重试 |
|
|
235
|
+
| Kubernetes 成功、数据库激活失败 | 旧 Head 继续路由;候选进入可重试或 GC 状态,不执行旧 workload retirement |
|
|
236
|
+
| 物理 schema expand 成功、候选部署失败 | 兼容列/索引保留并由 ledger 记录;旧逻辑 revision 不暴露它们,不做破坏性回滚 |
|
|
237
|
+
| 数据库激活成功、旧 workload 删除失败 | 新 Head 正常服务;记录告警并由幂等 GC 重试 |
|
|
238
|
+
| 相同 AppVersion 晋级 | 复用所有不可变修订;只切目标环境 Head 和该环境活动状态 |
|
|
239
|
+
| 授权摘要未变 | 环境 authzVersion 不变,旧 RoleSession 可继续;Head revision 仍因应用版本切换而增长 |
|
|
240
|
+
| 授权摘要改变 | 仅目标环境 authzVersion +1,并使该环境旧 RoleSession stale;其他环境不受影响 |
|
|
241
|
+
| 回滚 AppVersion | 创建新的 rollback DeploymentRun,CAS 切回历史不可变修订;运行态数据不倒退,兼容性 preflight 必须通过 |
|
|
242
|
+
| 配置数据定义回滚缺少新字段 | 旧逻辑修订忽略多余物理列;不删除列,不回滚业务数据 |
|
|
243
|
+
| PostgreSQL 不可用 | Head、授权和 Data API fail closed;不得从 Redis/Kubernetes 猜活动版本 |
|
|
244
|
+
| NodePort 被公网或同集群其他 Pod 直连 | 无有效网关断言时 Nest 在业务代码前拒绝;健康端点不返回配置、凭据或业务数据 |
|
|
245
|
+
| 候选容器启动后 Head 激活失败 | pending credential 不能取得业务 token,Worker/Scheduler 没有 lease;候选进入 retry/GC,不产生生产副作用 |
|
|
246
|
+
| Head 已切换但 runtime lease 暂时不可得 | 用户 API 仍按网关断言服务;后台停止领取新任务并告警,reconciler 恢复 lease,不让旧版本代领新任务 |
|
|
247
|
+
|
|
248
|
+
## 9. 2.0 Alpha 合同切换
|
|
249
|
+
|
|
250
|
+
用户已经明确 2.0 不承担当前 alpha 客户端兼容;稳定 1.x 在独立工具链和旧表上维护。因此这里采用一次明确的 contract cutover,不建设长期 alpha/native 双内核:
|
|
251
|
+
|
|
252
|
+
1. **K0 preflight evidence**:按显式 allowlist 交叉验证 Application v2 AppVersion/Head 闭包与 K3s managed workload,证明 prod-1 只有可重建的 alpha reference。只存在 legacy DeliveryRun/Runtime/Event 的应用必须被排除;不读取主体/业务值、不生成迁移计划。
|
|
253
|
+
2. **K1 build offline**:增加 runtime environment registry、最小原生 Head、immutable projection、native authz/data resolution 和新 Admin context,并在仓库/本地或隔离测试环境创建全新 app code 的 native reference;所有新路径先由测试/本地新应用验证,但尚不对线上 2.0 路由开放。alpha 路径继续只读旧 Head,native 路径只读新 Head,禁止请求级 fallback;1.x 代码不引用这些表。
|
|
254
|
+
3. **K2 capable rollout**:把同一 native-capable Platform Server 镜像部署到全部实例,读取实例 capability 清单;这时旧 2.0 路由仍保持 alpha,但新写入口关闭。readiness 不能代替全实例版本证明。
|
|
255
|
+
4. **K3 short write fence**:进入平台级 2.0 maintenance window,只暂停 Application v2 部署、Data API 写、role/scope 管理、Workflow/Event 新动作;1.x 和平台其他能力继续运行。等待在途 2.0 请求/worker lease 有界排空,撤销全部 alpha RoleSession 和 runtime OAuth token。prod-1 在此阶段只验证 Native artifact/Head 闭包与无业务凭据的 candidate readiness,不为测试建立 generation 旁路。
|
|
256
|
+
5. **K4 atomic contract switch**:数据库只做一次带 expected revision 的 `openxiangda_v2_contract_generation=native-2` CAS;新平台路由只读 Native 表,且只有已经绑定 Native Head 的新 reference 可路由,在验收窗口完成真实 Postgres/PostgREST/K3s/浏览器预发 E2E;旧 alpha endpoint 返回稳定 `410 OPENXIANGDA_V2_ALPHA_CONTRACT_REMOVED` 与最低客户端版本,不做 legacy serializer、双读或 alpha 数据导入。
|
|
257
|
+
6. **K5 verify and reopen**:新 reference 预发 E2E 通过后开放 Native 常规应用创建/写入,同一 Native AppVersion 晋级生产并再验收。观察期后仅撤销明确列出的旧 alpha runtime OAuth/principal 并删除两个旧 alpha K3s workload;alpha 数据库历史保留,1.x 共享表不删除。
|
|
258
|
+
|
|
259
|
+
回滚边界:K4 之前可回滚 native-capable 镜像;K4 后只回滚到同样理解 `native-2` schema/route 的修复镜像,不能恢复 alpha 授权语义或重新打开旧 endpoint。若 K4 后验收失败,保持 2.0 maintenance、修复或把 reference app Head 切回 native 历史版本;1.x 始终不进入该栅栏。
|
|
260
|
+
|
|
261
|
+
多实例门禁由平台进程自动心跳,无需应用开发者参与。每个实例上报 `PLATFORM_RELEASE_VERSION` 和固定 Native capability 集;单实例默认期望数量为 `1`,多实例部署只需把所有实例的 `OPENXIANGDA_PLATFORM_EXPECTED_INSTANCES` 设置为同一个总数。实例必须在短 TTL 内全部存活、release 完全一致且能力无缺失,`switch-native` 才能提交;`status` 同时返回逐实例证据。
|
|
262
|
+
|
|
263
|
+
## 10. 可证伪验收
|
|
264
|
+
|
|
265
|
+
1. 同一应用 preproduction 发布不同角色/字段策略后,production 的 RoleSession、capability explain 和 Data API 响应逐字节不变;local 开发不产生任何远程状态。
|
|
266
|
+
2. 同一 appCode 的原生 preproduction/production 拥有不同 environment UUID;同一 AppVersion 先预发后生产,两环境引用相同 immutable revision id/digest,但 RoleMembership、Secret、OAuth client 和 scope grant 不共享。
|
|
267
|
+
3. 两个并发部署使用同一 expected Head revision,恰好一个成功;失败者不能覆盖成功者。
|
|
268
|
+
4. 候选 runtime readiness 成功后强制让 activation SQL 失败,网关仍请求旧 AppVersion,候选随后被精确 GC。
|
|
269
|
+
5. 开发 config data resource 增加字段后物理列存在,但生产活动定义查询/写入该字段均拒绝;生产晋级后才可使用。
|
|
270
|
+
6. 回滚到旧 config revision 不删除物理列、不损坏新数据,旧逻辑字段集恢复。
|
|
271
|
+
7. PostgreSQL/Redis/Kubernetes 分别注入故障,证明 Head 和授权事实不从缓存或 annotation 推断。
|
|
272
|
+
8. 代表性 1.x 登录、角色、表单权限和 legacy environment-set 操作不访问任何新 2.0 原生表;2.0 CLI 不再调用 legacy environment-set API。
|
|
273
|
+
9. 直接访问活动、旧版和候选 NodePort:缺失、过期、错误 appVersion、错误 body digest 和重放断言全部在 Nest 业务 handler 前拒绝;正常网关请求仍通过 OAuth2/RoleSession/capability 判定,应用容器和访问日志中不存在调用者原始 access token。
|
|
274
|
+
10. invocation token 不能跨 app/environment/run、不能刷新;Head 切换、源会话撤销、RoleSession/membership stale 后均拒绝。真实 Nest→Data API 链路证明委托身份与原调用者的授权结果相同,但应用拿不到原始 bearer。
|
|
275
|
+
11. 候选 readiness 保持 10 分钟并人为触发 Scheduler tick/Worker 消息,Data API、外部副作用和任务领取计数均为零;Head 提交后只有新 run 获得 lease,回滚后新 run 停止领取。
|
|
276
|
+
12. 网关签名密钥轮换覆盖 current/previous 接受窗口、未知 key fail closed、时钟偏差上限和 20 MiB 流式摘要内存上限。
|
|
277
|
+
|
|
278
|
+
## 11. 实施顺序
|
|
279
|
+
|
|
280
|
+
1. CP0/CP1:按[Alpha 退役与 Native 切换前置审计](./native-kernel-inventory-v2.md)实现显式 allowlist classifier、Platform Server 镜像 `scripts/` 中的纯 Node 只读入口和根部署 preflight。它不增加 evidence schema/数据库角色/Secret/ServiceAccount/RBAC/通用 importer,不扫描业务表;根脚本复用 backend release Job 构造原语,从权威镜像摘要运行两个无 SA token 的短生命受限资源 Job,用 DB-A/K3s-A/DB-B/K3s-B 语义摘要证明 prod-1 只有可重建的 alpha reference,不部署候选镜像。`CP*` 不与本文 `P0-P4` 环境激活协议共享编号。
|
|
281
|
+
2. E1:按[原生配置投影实施蓝图](./native-configuration-projection-v2.md)建立 config/contracts v3、不可变配置编译器、最小原生 Head、authz/data/event/workflow/runtime-requirement 投影、contracts closure 及 idempotent digest 测试。
|
|
282
|
+
3. E2:Data API physical/logical 分离,先离线对照新旧 resolver,再随 native generation 一次按环境切读;不建立在线双读模式。
|
|
283
|
+
4. E3:原生环境授权内核,详见 [授权一致性基线](./authorization-consistency-v2.md)。
|
|
284
|
+
5. E4:抽取 Environment Activation service,加入 Head CAS、pending runtime credential、网关断言、单进程 runtime lease、失败候选 GC 和多实例 capability gate。
|
|
285
|
+
6. E5:执行短时 2.0 contract cutover,开放全新 app identity 的 native reference;不迁移旧 alpha reference,也不建设 alpha/native 长期兼容层。
|
|
286
|
+
7. E6:Admin A1/A2 消费 native RoleSession context,随后实现 Shell 产品轨。
|
|
287
|
+
|
|
288
|
+
每个阶段独立 SQL migration、平台提交和回滚说明;不得把 E1-E6 合成一次不可观察的大发布。
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# OpenXiangda 2.0 实施路线图与证据矩阵
|
|
2
|
+
|
|
3
|
+
状态日期:2026-08-15
|
|
4
|
+
|
|
5
|
+
本文只记录可由源码、自动测试、真实 HTTP、新应用黑盒或已发布工件证明的状态。文档存在不等于运行时完成,Mock 通过不等于平台集成完成,npm 已发布也不等于某个应用已经部署。
|
|
6
|
+
|
|
7
|
+
## 1. 状态定义
|
|
8
|
+
|
|
9
|
+
| 状态 | 含义 |
|
|
10
|
+
| --- | --- |
|
|
11
|
+
| 已交付 | 平台/工具链实现、正式门禁和独立应用证据均闭环 |
|
|
12
|
+
| 已实现待晋级 | 代码与门禁已通过,但尚未作为目标 AppVersion 晋级指定环境 |
|
|
13
|
+
| 已设计待确认 | 所有权、不变量、契约、失败恢复和验收矩阵已形成;根据架构门禁,确认前不修改运行时 |
|
|
14
|
+
| 未开始 | 尚无可以进入实现的确认设计 |
|
|
15
|
+
|
|
16
|
+
## 2. 能力矩阵
|
|
17
|
+
|
|
18
|
+
| 能力主题 | 唯一事实来源 | 当前状态 | 已有证据 | 尚缺内容与下一道门 |
|
|
19
|
+
| --- | --- | --- | --- | --- |
|
|
20
|
+
| OAuth2 外部应用身份 | Platform Server OAuth2 服务与数据库;Nest SDK 只消费 token | 已交付 | Client Credentials、租户/应用/环境/scope 绑定、一次性 Secret、轮换宽限、撤销、审计、跨环境拒绝、应用 Principal;平台 unit/持久化/真实 HTTP 和 Nest 测试通过 | 后续只按新 scope 或凭据策略单独设计,不与 Admin 用户会话混合 |
|
|
21
|
+
| 平台托管后端运行凭据 | Platform Server credential 状态 + Environment Head/DeploymentRun | 已交付功能基线,E4 激活语义待收敛 | CAS 暂存、同 AppVersion 滚动部署、旧凭据宽限、重试幂等、CLI 不获得明文;真实 HTTP 两轮通过 | 当前候选凭据仍可能在 Head 前进入可用链路。E4 改为 pending→active→retiring→revoked,业务 token/后台 lease 只授予当前 Head 对应 run;配额、告警另做运维主题 |
|
|
22
|
+
| 应用 Secret | Platform Server Secret 版本与审计表 + runtime activation policy | 已交付功能基线,active-only 注入待收敛 | 环境级 AAD、只写值、不可变版本、CAS 幂等、required/optional 部署语义、删除和审计测试 | P0 只校验所需版本存在,不向 pending runtime 提前注入 active-only Secret;E4 通过短时 runtime identity 按需读取并受 egress policy 约束。KMS 替换保持同一协议,不在应用端增加第二套存储 |
|
|
23
|
+
| Data API / App API | Platform Server 物理 schema + 环境 Head 选择的逻辑 config projection;应用后端承载业务逻辑 | Native 调用委托与请求路径证明已交付;逻辑配置投影继续收敛 | 用户与服务 Principal、PostgREST RLS、字段/行权限、受限事务批处理、最长 60 秒 invocation token、最长 30 秒 Ed25519 assertion、Nest 全局 transport guard 与同构本地平台已通过 | 继续完成 E2 physical/logical 分离和 E4 runtime lease;NodePort/NetworkPolicy 只缩小暴露面,不参与授权正确性 |
|
|
24
|
+
| RBAC + 业务关系权限 | Platform Server 原生 authz revision、环境 state、RoleMembership、RelationshipGrant、RoleSession | A0-N0/N1/N2/C1/C2/P 已实现 | N2 新建环境级 manual role/membership/delegation/relationship、应用级 super-admin grant、Native RoleSession 与不可变 mutation receipt;C2 每请求先以 PostgreSQL 校验 session/state/主体,再使用环境、authz/scope 版本与主体 revision 隔离的 Redis summary;P 以 `scopeSources` 将学院管理员、仪器管理员等业务表映射为环境范围 grant,Data API 同事务置 stale/建任务,Worker 提供 lease/retry/DLQ/replay/receipt/closure,strict 投影失败关闭且恢复扫描不依赖 Redis | 下一步 A0-N3/N4 完成全实例 gate、Native generation switch 与真实 PostgreSQL/PostgREST/K3s 验收;Admin A1 只消费 native kernel |
|
|
25
|
+
| Events/Automation v2 | Platform Server delivery/receipt 状态;Nest handler 承担业务副作用 | 已交付 | CloudEvents 签名、claim lease、重复投递、进程崩溃接管、stale token 隔离、重试/DLQ/replay、双密钥、重启恢复与持久 receipt 真实链路两轮通过;Native 本地层已由 CLI 新建独立应用,在真实 PostgreSQL 上覆盖 503 + Retry-After、确定性 400 死信、幂等重放和成功回执;Timer 使用严格六段 Cron + IANA 时区,调度、事件、outbox 与下一触发点在同一事务提交,并通过同一计划时间并发 claim、重启恢复与 reset 验收 | 外部副作用仍必须使用 event id 做下游幂等;不承诺跨系统 exactly-once;停机期间的 Timer 采用合并补偿而非历史洪峰补发 |
|
|
26
|
+
| Workflow Kernel v2 | Platform Server 持久状态机;Admin 只解释 Surface 协议 | 进行中 | 本地独立应用已在真实 PostgreSQL 验证预览令牌单次消费、实例/任务/参与者链/时间线/回执事务提交、跨重启幂等回放、实例与任务版本 CAS、并发审批唯一成功和 reset 清理;转交与任务代理真实切换 RoleAssignment 待办归属,前/后加签使用有序参与者链,目标身份按角色和数据 scope 校验;长期代理按半开时间窗和不重叠规则解析,在预览与任务创建间 CAS 校验,把规则、有效期、原审批人及代理身份冻结进参与者链,撤销仅影响新任务;浏览器使用独立测试用户覆盖代理待办归属与冻结审计 | 远端 Kernel 与 1.x 双核运行、远端长期代理事务和过期任务管理员重分派仍需继续验收 |
|
|
27
|
+
| Workflow Kernel v2 | Kernel 状态机与平台持久实例;业务字段仍在 Data/App API | 已交付基线 | 同意/拒绝、转交、回退、撤回、加签、代理、两种退回语义、重新提交、长任务委托、Provider 恢复与并发 CAS/lease 真实链路通过 | 可视化编辑器和更多业务协议按独立需求设计;不把业务字段迁入流程库 |
|
|
28
|
+
| 独立 NestJS 后端与应用交付 | 应用 Git/AppPackage;Platform Server DeploymentRun/Environment Head | 已交付功能基线,E4 激活边界待实现 | 不可变包摘要、后端 OCI digest、版本化 Kubernetes workload、readiness、重试/取消/回滚/晋级和资源限制已有证据 | 候选当前可能先取得可用 runtime credential,同进程 Worker/Scheduler 缺活动 Head lease,共享 NodePort 缺强路径证明。E4 以 pending credential、短租约、gateway assertion、failed candidate GC 收敛;仍保持每应用独立容器,不拆成多应用 Node 进程或强制三个 Deployment |
|
|
29
|
+
| 2.0 CLI/MCP/Skills/模板 | 独立 `openxiangda-v2` 仓库与已发布 npm 工件 | 已交付基线 | 16 包 check/test/build、边界扫描、真实 tarball 新应用、持久 reference app、Chromium、Skills、文档全部通过;2026-08-15 再以 12 个候选 tarball 经临时本地 registry 安装到仓库外参考应用,生成、类型检查、单测、真实 NestJS OAuth2/Native 身份联调与生产构建全部通过;不调用 1.x | 新能力必须同时更新命令/MCP/Skill/模板消费证据,禁止只写 CLI 命令 |
|
|
30
|
+
| 确定性工具链发布 | Changesets 版本提交、冻结工件清单、release receipt | 已交付 | 待消费 Changeset 在构建前 fail-fast;版本只由 Changesets 物化;正式发布只验证一次同一批 tarball;CLI alpha.20 与 skill-kit alpha.19 已按该状态机发布并打 Git tag | 周期性全量审计保留;不得恢复人工选包、手工版本或重复正式门禁 |
|
|
31
|
+
| 环境配置内核 E0-E6 | AppVersion/component revision + native Runtime Environment + minimal Environment Head + 环境运行态 | E1-C0/C1/S0/S1/T0 已完成 | breaking config/contracts v3、平台纯编译器、六领域不可变投影、聚合投影、AppVersion binding、compile receipt、精确 artifact shadow prepare 与真实 PostgreSQL 并发/来源防伪/绑定后不可变已经通过;全新 `openxiangda-v2-native-reference-app` 由候选 tarball 创建并完成 check/test/build,连续构建逐字节一致,config/contract v3 闭包和 artifact/manifest 篡改拒绝已进入发布门禁 | 下一步进入 A0 Native 环境授权;随后实现 Data physical/logical、最小 Head CAS、pending credential、调用委托/网关断言、runtime lease、候选 GC 与 generation cutover。旧 alpha 只留审计历史,不做双读、双写或导入 |
|
|
32
|
+
| 授权内核 A0-N/C/P | 不可变 authz revision + 环境 authz state + native role/scope 表 | 已确认实施;N0/N1/N2/C1/C2/P 完成 | N1/C1 建立不可变定义、两环境 state 与原子版本;N2 建立独立 Native 运行表与局部撤权;C2 建立 DB-authoritative evaluator、request cache、环境/版本 cache namespace、边界 TTL 与 RelationshipGrant 直读;P 升级 `native-2` 配置契约并建立 source definition、projection state/job/receipt/value/closure/effective grant、Data API 与 membership 原子失效、冷启动恢复与 strict gate;89 个 SQL migration 校验、35 个 2.0 migration 真实 PostgreSQL 幂等应用、39 个平台套件 / 263 项测试和工具链全 workspace 测试通过 | 当前推进 N3-N5。alpha membership/grant 不复制、不迁移,禁止给 legacy 表补 environmentKey 或建立长期双读/双写 |
|
|
33
|
+
| Admin Shell v2 完整框架 | 平台 RoleSession 上下文 + Admin Core | 实施中 | A1/A2 Native 身份、UX1、B1.1 结构化 route manifest、B1.2 标准记录页/资源失效、B1.3 流程业务提交适配、B2 有界保活、C 个人中心贡献化、D Data Workbench controller、DirtyStateRegistry/实例释放门禁、标准流程工作中心与 Kernel 服务端分页已落地;标准列表与特殊工作台共享身份隔离的 DataQuery 状态机,Core-only 构建不含 Workflow 代理;角色选择器只在选中态展示稳定角色名、在下拉项有界展示 scope,数据与流程页面隐藏协议码并展示业务标签;全新 tarball 应用在显式安全 reset 后以 8 个 Chromium 场景一次穿过 React、本地平台、NestJS 与真实 PostgreSQL | 安全退出的 B0-R 平台接入仍待完成;安全轨和产品轨在完整生产验收前汇合 |
|
|
34
|
+
| Admin A1 RoleSession 上下文 | Native RoleSession bootstrap 是唯一身份资料/role subject/scope 来源 | 已设计待确认 | SubjectProfile 字段白名单、tenant 联合查询、非 active scope=null、capability=`authz.role-session-context`、有界 role subject 分页与切换 CAS 已定义 | A0 native cutover 后实现;A1 自身不再增加身份存储,但不得建立在 alpha 全局 assignment 上;平台先发,工具链后要求 capability |
|
|
35
|
+
| Admin B0-O 租户公共 Origin | Platform Server Origin module + 版本/head/hostname claim registry | 已设计待确认 | 全仓确认多套模糊解析和广泛 URL 调用者;prod-1 证实 HTTPS/HTTP 配置差异、未登记 vhost 别名,且生产 `default_configs` 没有源码宣称的复合唯一约束;稳定租户 UUID、不可变 staged→challenge→verified→active、head revision CAS、hostname claim、事务审计、全局兼容阶段+单租户事实源状态、无长期双写和[逐文件实施蓝图](./tenant-public-origin-implementation-blueprint.md)已定义 | 确认后先做 O0 只读 inventory/digest 与 plan validator;操作者显式决定 migrate/decommission,所有服务实例同版后进入 migrating,再逐租户冻结/验证/切换,单租户失败不阻塞全平台;B0-R 不得绕过该阶段 |
|
|
36
|
+
| Admin B0-C Cookie 安全 | Platform Server `AuthCookieService` + 无状态 policy resolver | 已设计待确认 | 已确认当前 DOMAIN JSON 同时决定 Cookie Domain 且 `secure:false`;共享会话/协议 Cookie 所有权、HTTPS Secure/`__Host-`/host-only、版本化名称、legacy scope manifest、C0/C1/C2 状态机和旧 host retirement 已定义 | B0-O registry 稳定后独立实现;首期不支持跨子域共享;C1 后只能回滚到理解 v2 Cookie 的兼容镜像,不和 Origin 数据迁移、return target 或 OAuth state 混发 |
|
|
37
|
+
| Admin B0-R 登录 return target 安全 | 平台通用登录代理和服务端 CAS 校验 | 已设计待确认 | 全仓审计确认平台、1.x View、流程、旧编辑器和 CLI 合法生产者均可归入同源;一次解析、8 KiB 上限、精确 origin、CAS 双阶段规范化、错误链路 fail closed 及回归向量已定义 | 明确确认后作为独立平台安全提交,不和 Shell 改造、OAuth state 或身份协议混发 |
|
|
38
|
+
| 旧平台第三方认证 S0 | Platform Server 一次性 OAuth state 与 purpose 绑定 | 未开始 | 已证明旧登录 state 仅为 tenantId、回调未消费 state,账号绑定指向未注册 `/bind-callback` 且 tenantId 为空 | 先单独形成登录/绑定事务、TTL、一次性消费和迁移设计;不得把它混入 B0-R,也不重做 2.0 Client Credentials/App Auth |
|
|
39
|
+
| Admin B1-B2/C/D | Admin Core 与显式可选 contribution | 实施中(B1.1/B1.2/B1.3、B2 生命周期门禁、C、D 与工作中心分页已完成) | anyOf/allOf、manifest 闭包、祖先访问继承、直接 URL 403、独立数据表单/详情、资源级非持久失效、业务保存后确定性流程预览、工作中心统一页头/中文状态/过期请求隔离和 `total/limit/offset` 服务端分页、DirtyStateRegistry/onBeforeDispose、12 标签/6 keepAlive LRU、身份/manifest 隔离恢复均已实现;D 以 `identityScope + identityEpoch + resource + configVersion` 隔离 query/page/selection,generation 拒绝旧响应,标准列表复用同一 controller;个人中心复用同一 access DSL,Workflow 代理由独立子路径延迟贡献且 Core-only 构建负向证明通过;标准退出等待 B0-R | 继续统一标准页面细节与安全退出;禁止临时全局状态、角色名判断、未绑定环境的缓存键和第二套查询 DSL |
|
|
40
|
+
|
|
41
|
+
## 3. 当前关键路径
|
|
42
|
+
|
|
43
|
+
```mermaid
|
|
44
|
+
flowchart LR
|
|
45
|
+
Audit["B0-R 全仓生产者审计(已完成)"] --> Confirm["确认 Admin Shell v2 设计"]
|
|
46
|
+
Confirm --> CP["CP0/CP1 alpha 退役 preflight"]
|
|
47
|
+
CP --> E1["E1 Native config/contracts v3"]
|
|
48
|
+
E1 --> T0["E1-T0 全新 Native reference(已完成)"]
|
|
49
|
+
T0 --> E2["E2 Data API physical/logical 分离"]
|
|
50
|
+
T0 --> A0["A0 native environment authz"]
|
|
51
|
+
E2 --> E4["E4 Head CAS / 调用委托 / runtime gate"]
|
|
52
|
+
A0 --> E4
|
|
53
|
+
E4 --> Cutover["E5 native generation 与 reference app cutover"]
|
|
54
|
+
Confirm --> B0O["B0-O 租户 canonical origin 收敛"]
|
|
55
|
+
Cutover --> A1["A1 平台 RoleSession 上下文"]
|
|
56
|
+
B0O --> B0["B0-R 平台 return target 收敛"]
|
|
57
|
+
B0O --> B0C["B0-C Cookie 安全迁移"]
|
|
58
|
+
A1 --> A2["A2 identityScope / epoch"]
|
|
59
|
+
B0 --> B1["B1 路由/主题/安全退出"]
|
|
60
|
+
A2 --> B1
|
|
61
|
+
A2 --> B2["B2 标签与有界 keepAlive"]
|
|
62
|
+
B1 --> C["C 个人中心贡献化"]
|
|
63
|
+
A2 --> D["D Data Workbench controller"]
|
|
64
|
+
B0C --> Fresh["完整新应用 Chromium 验收"]
|
|
65
|
+
B1 --> Fresh
|
|
66
|
+
B2 --> Fresh
|
|
67
|
+
C --> Fresh
|
|
68
|
+
D --> Fresh
|
|
69
|
+
Fresh --> Release["Changesets 物化与单次正式发布"]
|
|
70
|
+
Release --> Promote["同一 AppVersion 预发/正式晋级"]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
当前没有证据支持重写 Admin,也没有必要重新实现 OAuth2、Secret、Events 或 Workflow 基线。源码与 prod-1 只读审计证明 A1 不能建立在现有全局授权投影上,同时也证明不需要把 alpha reference 搬入 Native:平台数据/授权轨先做 CP0/CP1 显式 allowlist preflight,随后从空状态实现 E1/A0;具体隔离与退役合同见[Alpha 退役与 Native 切换前置审计](./native-kernel-inventory-v2.md),bundle v3、最小 Head、不可变投影和激活事务边界见[原生配置投影蓝图](./native-configuration-projection-v2.md)。安全轨从 B0-O0/O1 开始;A1 要等待全新 native reference 环境切换。各轨形成可回滚提交,B1 的安全退出仍等待 B0-R,完整生产晋级同时通过 B0-C。不能为了某个页面先改缓存、角色判断、登录跳转或给 legacy 表临时加环境分支。
|
|
74
|
+
|
|
75
|
+
## 4. 每轮执行记录要求
|
|
76
|
+
|
|
77
|
+
后续每轮在修改前必须给出:问题证据、能力所有者、不变量、上下游契约、并发与失败语义、安全/资源上限、回滚单元、可证伪验收。修改后把实际证据回填到本矩阵;若证据与设计冲突,先更新设计并重新确认,不用局部兼容分支掩盖冲突。
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# OpenXiangda 2.0 本地开发内核
|
|
2
|
+
|
|
3
|
+
状态:L0-L3 已实现,并由打包工具链创建的全新应用在真实 PostgreSQL 上验证;本轮补齐可审计的 status/stop/reset 生命周期控制
|
|
4
|
+
|
|
5
|
+
## 1. 产品结论
|
|
6
|
+
|
|
7
|
+
开发者在新建应用后只需要运行:
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
openxiangda dev
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
命令打开完整 Admin 应用并同时启动 React/Vite、NestJS 和本地平台服务。前端、后端、生成契约和应用声明支持有界热更新;开发者不需要先 provision、登录远程平台或创建第三个远程环境。
|
|
14
|
+
|
|
15
|
+
远程部署环境只有 `preproduction`、`production`。`local` 是进程运行模式,不是 environment registry 记录:
|
|
16
|
+
|
|
17
|
+
```text
|
|
18
|
+
local source + local state
|
|
19
|
+
| build once
|
|
20
|
+
v
|
|
21
|
+
immutable AppVersion -> preproduction Head -> production Head
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
因此公共合同必须区分:
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
type LocalRuntimeMode = "local";
|
|
28
|
+
type DeploymentEnvironment = "preproduction" | "production";
|
|
29
|
+
type RuntimeMode = LocalRuntimeMode | DeploymentEnvironment;
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
CLI 不提供 `deploy development`、`deploy local` 或 `promote preproduction` 的反向路径;production 只接收已经在 preproduction 验证过的同一 AppVersion/package/image digest。
|
|
33
|
+
|
|
34
|
+
## 2. 架构门禁
|
|
35
|
+
|
|
36
|
+
| 维度 | 决定 |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| 问题证据 | 当前模板已有 Vite 内存平台模拟器和并行 `pnpm dev`,但 Nest 默认仍把环境写成 `development`,Data/Workflow/Event 状态主要在前端进程中模拟;这足以演示页面,不能证明真实 PostgreSQL、进程重启、前后端调用和安全边界。 |
|
|
39
|
+
| 能力所有者 | `openxiangda dev` 是本地生命周期唯一编排者;本地平台服务拥有身份、Data API、Workflow/Event 和开发状态;Vite 只负责页面/HMR,Nest 只负责应用业务接口。应用代码不自行启动数据库或伪造平台 token。 |
|
|
40
|
+
| 稳定不变量 | local 永不出现在远程 registry/Head/DeploymentRun;AppPackage 不含目标环境;本地状态和凭据不发布;同一源码生成的合同由本地测试和远程运行共同消费,不维护第二套业务 API。 |
|
|
41
|
+
| 上下游合同 | CLI 生成一个 loopback manifest,向 Web/Nest 注入 local base URL、app identity 和短期 local credential;Data API、Admin context、Workflow/Event 的 HTTP/JSON 合同与 Native v3 一致,只有 issuer、存储生命周期和副作用策略不同。 |
|
|
42
|
+
| 并发与失败 | 每个工作区按 canonical path digest 持有单实例锁、动态端口和本地状态目录;第二个 `dev` 返回已有 URL,不重复启动。任一子进程失败时有界终止整组进程并保留诊断,不调用远程写接口。`dev stop` 通过 PID + 进程启动身份防止 PID 重用误杀;`dev reset --data` 与启动争抢同一锁,不能边运行边删除。重启恢复持久开发数据,显式 reset 才精确重建当前工作区状态。 |
|
|
43
|
+
| 安全与资源 | 服务默认只监听 `127.0.0.1`;local token 固定 local issuer/audience 且不能被远程平台接受,远程 OAuth/Secret 不自动导入。PostgreSQL 容器有磁盘/内存预算;本地数据库凭据、OAuth 原始 Secret 和加密主材料只在权限为 0600 的 Git 忽略目录保存,原始 OAuth Secret 只注入 NestJS,日志与响应不输出 token/Secret。生产 build 静态证明不包含 local middleware。 |
|
|
44
|
+
| 回滚单元 | L0-L3 都只改变 CLI、模板和本地测试;不修改线上 Head。异常时可回退本地工具版本。数据库数据只由带所有权验证的 `dev reset --data` 删除;手工删除 `.openxiangda/local` 不是受支持的数据回滚方式,因为会丢失凭据和资源绑定证据。 |
|
|
45
|
+
| 可证伪验收 | 从空目录创建应用后一个命令打开完整 Admin;前端和 Nest 修改均热更新;真实 PostgreSQL 事务/RLS/幂等测试通过;停止/重启保持数据、`dev reset --data` 清空;伪造会话或 Docker 标签时零删除;local token 调远程和远程 token 调 local 均拒绝;production bundle 对 local canary 零命中。 |
|
|
46
|
+
|
|
47
|
+
## 3. 默认本地拓扑
|
|
48
|
+
|
|
49
|
+
```text
|
|
50
|
+
Browser
|
|
51
|
+
-> Vite/Admin (loopback, HMR)
|
|
52
|
+
-> /service -> Local Platform Service
|
|
53
|
+
-> RoleSession/OAuth verification
|
|
54
|
+
-> /openxiangda-app-api -> NestJS application
|
|
55
|
+
-> Data/OAuth/Secret -> PostgreSQL
|
|
56
|
+
-> local workflow/event dispatcher
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
- CLI 先执行增量 `generate` 并确认生成成功,再启动依赖;生成失败不会带着旧合同继续运行。
|
|
60
|
+
- CLI 自动选择端口并打印/打开一个 URL;Admin 的菜单、路由、标签页、个人中心和角色切换都走同一页面入口。
|
|
61
|
+
- Local Platform Service 是 Node 进程,不嵌入 Vite middleware。这样 Nest、浏览器和测试共享同一个平台状态,Vite 重启不会丢失后端事实。
|
|
62
|
+
- PostgreSQL 是默认平台状态存储。开发者不安装 PostgreSQL 服务、客户端、用户或建库脚本;只需 Docker Desktop 或兼容容器运行时。CLI 启动摘要固定的 PostgreSQL 16 镜像,按 canonical workspace 创建独立容器和 volume,自动完成建库、迁移、健康检查与 schema digest 校验,不复用任意同端口数据库。
|
|
63
|
+
- 无容器运行时时,默认命令返回明确诊断;显式 `--ui-only` 才允许使用无持久性的页面模拟器,并在页面持续显示“UI-only,不可作为平台验证”的标识。CI、`openxiangda test` 和发布检查从不接受 UI-only 证据。
|
|
64
|
+
|
|
65
|
+
### 3.1 2026-08-14 Native App API 用户身份闭环
|
|
66
|
+
|
|
67
|
+
| 门禁 | 决定 |
|
|
68
|
+
| --- | --- |
|
|
69
|
+
| 问题与证据 | 完整本地会话中,Admin 已使用 `openxiangda.native-role-session/v2`,App API 网关也签发绑定该会话的 60 秒 loopback token;Nest SDK 却继续请求 alpha `/authz/principal`。本地验证器最终又把 token 中的 Native session id 与 `openxiangda.role-session/v2` 比较,导致真实 Chromium 流程保存稳定返回 `401 LOCAL_USER_IDENTITY_INVALID`。 |
|
|
70
|
+
| 能力所有权 | Native RoleSession 服务是 2.0 用户身份的唯一所有者;网关只签发和转发短期调用身份;`openxiangda-nest` 只通过平台 `/native/authz/principal` 与 `/native/authz/explain` 校验并注入 Native Principal/RoleSession,不建立映射或兼容状态。 |
|
|
71
|
+
| 稳定不变量 | 浏览器原始 Authorization 不进入应用;token 必须绑定 app、`runtimeMode=local`、Native RoleSession、60 秒 TTL 和随机 nonce;Nest handler 只收到平台重新验证后的 Native identity。应用身份 OAuth2 仍走独立 `/oauth2/principal`,不允许用无 RoleSession 的服务身份冒充用户。 |
|
|
72
|
+
| 上下游契约 | `openxiangda-nest` 的用户上下文改为 `NativePrincipal + NativeRoleSession`,SDK 用户授权路径固定为 `/native/authz`;本地平台补齐同形 principal 端点;官方 Nest 模板的 controller/service 类型同步改为 Native 类型。2.0 不增加 alpha fallback 或运行时开关。 |
|
|
73
|
+
| 失败与并发 | 缺失、签名错误、过期、错误 app 或 stale Native RoleSession 均在业务 handler 前拒绝。角色切换后旧 token 即使尚未到期也因 session id 不匹配失败;授权 explain 必须再次校验同一 Native session,写操作不自动重放。 |
|
|
74
|
+
| 安全与资源上限 | loopback token 固定 60 秒且只接受 HMAC 等长比较;payload 不携带 allow 结果、业务数据或 Secret。principal/explain 仍受 SDK 5 秒默认超时约束,不增加缓存或后台刷新。 |
|
|
75
|
+
| 回滚边界 | 只发布 `openxiangda-local-platform`、`openxiangda-nest` 和包含新类型消费的创建模板;本地数据 schema、远程 Head、1.x SDK 与旧应用无变化。回滚时三个工件作为同一测试 BOM 恢复。 |
|
|
76
|
+
| 可证伪验收 | 单测断言 Nest 请求 Native 路径并注入 Native schema;本地平台拒绝伪造/错误 session token;全新独立应用在真实 PostgreSQL 下通过 App API 保存、流程预览、发起和后续审批 Chromium 场景。 |
|
|
77
|
+
|
|
78
|
+
### 3.2 2026-08-14 确定性浏览器验收边界
|
|
79
|
+
|
|
80
|
+
| 门禁 | 决定 |
|
|
81
|
+
| --- | --- |
|
|
82
|
+
| 问题与证据 | Playwright 复用日常开发会话时,历史修改会污染种子数据断言;若测试配置自行删除数据库,又会绕过 CLI 对工作区、容器和 volume 的所有权校验。先跑 UI-only、再跑完整会话还会重复同一批浏览器成本,却不能增加后端证据。 |
|
|
83
|
+
| 能力所有权 | 日常 `openxiangda dev` 永远保留开发数据;外部 `OPENXIANGDA_E2E_BASE_URL` 只连接现有会话且零清理;发布门禁由打包工具链创建一次性外部应用,并且只能通过 `openxiangda dev reset --data` 建立确定性初始状态。 |
|
|
84
|
+
| 稳定不变量 | 浏览器测试不拥有任意开发者 volume。一次性验收应用完成重置后,Chromium 只运行一次,但路径必须同时穿过 React、独立本地平台、NestJS 与真实 PostgreSQL。UI-only 只服务快速页面回归,不能重复计作完整证据。 |
|
|
85
|
+
| 失败与回滚 | reset、进程就绪或任一浏览器场景失败立即终止门禁并保留诊断;一次性工作区与其有所有权标签的资源是唯一清理边界。回滚只需恢复打包验证器,不改变开发数据和线上环境。 |
|
|
86
|
+
| 可证伪验收 | 被历史修改污染的持久会话不再用于种子套件;全新 tarball 应用在安全 reset 后通过 8 个 Chromium 场景、重启持久化、事务幂等、并发、事件重放、Workflow/Timer 恢复和最终隔离检查。 |
|
|
87
|
+
|
|
88
|
+
## 4. 本地身份与状态
|
|
89
|
+
|
|
90
|
+
- 本地平台为当前工作区生成短期签名材料和 runtime OAuth client,保存在 `.openxiangda/local/`,目录必须被 Git 忽略。原始 OAuth Secret 只注入 NestJS 子进程;Web 子进程和 manifest 只能看到非敏感元数据。
|
|
91
|
+
- 默认提供开发者、应用最高管理员以及应用声明的示例角色;切换角色仍生成稳定单角色 RoleSession,不能在前端直接替换 role code。
|
|
92
|
+
- local Principal 明确包含 `runtimeMode=local`,不伪造 preproduction/production environment UUID。
|
|
93
|
+
- Secret 值只通过本地管理 API 从一次性表单或显式本机输入进入 PostgreSQL 加密存储,不写 config、AppPackage、日志或浏览器持久存储;管理 API 只返回元数据,创建、轮换、幂等回放和审计均不回显明文。
|
|
94
|
+
- 本地状态只属于当前工作区。复制仓库不会沿用 identity;工作区 canonical path 改变时需要显式 `dev adopt` 或重新创建,防止两个目录同时写同一 volume。
|
|
95
|
+
|
|
96
|
+
## 5. Data、Workflow 与 Events
|
|
97
|
+
|
|
98
|
+
本地开发分清“快速交互”和“发布证据”,但不分裂合同:
|
|
99
|
+
|
|
100
|
+
1. 页面、Nest App API、Data API 都调用 Native v3 路径和 DTO。
|
|
101
|
+
2. Data API 在本地 PostgreSQL 上执行真实 schema、事务、约束、RLS 与幂等逻辑;应用仍不能拿数据库凭据。
|
|
102
|
+
3. Workflow Kernel 使用相同定义编译器和命令状态机,本地提供可控时钟、审批人和操作面板;业务字段仍存 Data API/App API。
|
|
103
|
+
4. Event 使用与 Data API 写事务一致的持久 outbox、带租约的 delivery 和平台 receipt。暂停订阅后既不生成新 delivery,也不领取已有待处理 delivery;恢复后继续领取。网络错误、408、409、425、429、5xx 按有界指数退避,`Retry-After` 优先,其他 4xx 直接死信;领取代次 CAS 阻止过期 Worker 覆盖新结果;同一原投递与重放幂等键只创建一个 delivery。handler 调用真实 Nest endpoint,应用重启后 receipt 继续抑制重复副作用。
|
|
104
|
+
5. Timer 使用持久 schedule/firing 记录和严格六段 Cron;IANA 时区、DST 计算与声明校验由成熟解析器完成。到期事件与 schedule 推进、outbox、delivery 在同一 PostgreSQL 事务内提交,多实例通过行锁与计划时间 CAS 保证同一计划时间只触发一次。停机恢复将遗漏时段合并为一次触发,避免产生无界补偿洪峰;Cron 或时区声明变化会原子重算下一次执行。`openxiangda event timer fire <timer-id>` 只对完整本地开发会话开放,用于无需等待真实时间的端到端验证,仍走相同持久化和 Nest 投递链路。
|
|
105
|
+
6. 任何只在 UI-only 模式中通过的场景都不能满足 `openxiangda check` 或 `openxiangda test` 的集成项。
|
|
106
|
+
|
|
107
|
+
## 6. 命令面
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
openxiangda doctor
|
|
111
|
+
openxiangda dev [--no-open]
|
|
112
|
+
openxiangda dev status
|
|
113
|
+
openxiangda dev stop
|
|
114
|
+
openxiangda dev reset --data
|
|
115
|
+
openxiangda dev --reset
|
|
116
|
+
openxiangda dev --ui-only
|
|
117
|
+
openxiangda event timer fire <timer-id>
|
|
118
|
+
openxiangda test
|
|
119
|
+
openxiangda deploy preproduction
|
|
120
|
+
openxiangda deploy production --package <package-digest>
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
日常开发不要求记忆容器编排命令、端口、数据库连接或环境 ID。`doctor` 会把 Docker CLI 缺失与 daemon 不可用作为完整本地开发的阻断诊断,同时返回本地会话摘要。`dev status` 不启动 Docker,`dev stop` 保留 volume,`dev reset --data` 是唯一独立的数据删除入口;它先解析全部目标并验证容器 runtime/credential 标签和 volume workspace/runtime 标签,再删除任意目标。高级诊断仍保存在 `.openxiangda/local/diagnostics.json`,内部 Docker 参数不扩展为公共命令。
|
|
124
|
+
|
|
125
|
+
## 7. 实施顺序
|
|
126
|
+
|
|
127
|
+
1. **L0 合同切分(已完成)**:Native 合同 `2.0.0-alpha.2` 已删除远程 `development`,引入 `RuntimeMode=local`;模板与测试不再把 local 伪装成远程环境。新 AppPackage 只能部署到 preproduction,同一 AppVersion 只能晋级到 production,回滚只允许两个远端环境。OAuth 等运维轮换使用受当前 Head 和成功运行历史约束的 `redeploy`,不再滥用 promotion。平台所有 Native 远端入口共用同一 fail-closed 环境断言;SQL 合同拒绝新写入 `development`,但保留既有 alpha 行作为不可变审计历史。
|
|
128
|
+
2. **L1 生命周期(已完成)**:CLI 已实现按工作区单实例锁、动态端口、进程组监督、就绪后浏览器打开、结构化诊断、日志捕获和安全退出;`--local-sdk` 使用发布同形的 npm tarball 工件,避免源码链接产生多份 React/NestJS 运行时。全新独立应用已经通过 CLI 自身的 check/test/build 以及真实 Vite + NestJS 启停验证。
|
|
129
|
+
3. **L2a 独立本地平台(已完成)**:完整模式已把 Vite 内存模拟器抽成独立 loopback 服务;CLI 分别监督应用进程组和平台进程,浏览器通过 Vite 代理、NestJS 通过平台 URL 访问同一状态。任一进程失败会终止整组,第二次启动复用会话,动态三端口和退出清理已在全新独立应用验证。日常开发以 `openxiangda dev` 为唯一受监督入口;内部 `pnpm dev` 只供 CLI 编排,不作为公开入口。
|
|
130
|
+
4. **L2b 持久本地平台(已完成)**:本地平台内核已从应用前端模板移入独立 `openxiangda-local-platform` 工具链包;CLI 使用摘要固定的 PostgreSQL 16 镜像,为每个 canonical workspace 建立带所有权标签的容器与 volume,并限制为 1 CPU、512 MiB、200 PID。数据库只绑定 loopback,凭据保存在 Git 忽略且权限为 0600 的工作区状态目录,声明通过文件而非环境变量传递。Data API 的记录、审计、乐观 revision、受限事务、幂等凭证与活动角色已经持久化;授权从应用声明的 RBAC、数据范围、关系授权和字段策略统一求值。runtime/external OAuth client、短期应用 token、应用身份 Data API、加密 Secret、CAS/幂等/审计也使用同一控制存储。浏览器 App API 经过平台网关,浏览器 token 不会直传,网关签发绑定 app 与当前 RoleSession 的 loopback 身份给 NestJS。完整停止再启动保持业务数据、Secret 元数据和 OAuth 凭据,只有 `--reset` 在所有权核验后精确重建当前工作区 volume。发布计划只在这些能力或关联模板发生变化时,使用真实打包工件和全新应用重跑数据库生命周期门禁,不让无关包重复承担该成本。
|
|
131
|
+
5. **L3a Events(已完成)**:编译器生成订阅代码契约,CLI 为每项声明生成仅注入 Nest/本地平台的 0600 签名材料;Data API 与 outbox 同事务提交,dispatcher 使用 `SKIP LOCKED`、领取租约、代次 CAS、状态分类、`Retry-After`、有界退避、死信与幂等重放。Nest 默认使用平台持久 receipt;完整停止/重启和 `--reset` 行为已通过真实打包工件创建的新应用、真实 PostgreSQL、真实 Nest endpoint 与 Chromium 验证。
|
|
132
|
+
6. **L3b Timer / Workflow(已完成)**:Timer 已实现严格六段 Cron/IANA 时区校验、持久 schedule/firing、同事务 outbox/delivery、暂停、重启恢复、停机合并、声明变更重算和仅限本地会话的 CLI 快进。两个绑定同一 `expectedScheduledAt` 快照的并发 claim 只生成一个 firing;连续执行“触发下一次”则按定义推进到不同计划时间。Workflow Kernel 已实现定义编译、预览令牌、实例/任务/时间线/回执事务提交、实例与任务版本 CAS、同幂等键重放、同任务并发唯一成功、转交、前后加签、回退、角色/数据 scope 审批人解析,以及带有效期和不重叠约束的长期代理;任务创建时冻结原审批人、代理规则和最终参与者,撤销规则不改历史任务。完整停止/重启保持进度,显式 reset 精确清空。
|
|
133
|
+
|
|
134
|
+
每一步只在本地新建应用和真实打包工件中验证。L0 与 Native E1 是同一个 breaking contract 变更,不能先加兼容枚举再二次迁移;L2/L3 不阻塞 CP0-CP2 只读退役审计。
|