@h-ai/cli 0.1.0-alpha.67 → 0.1.0-alpha.68

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.
Files changed (164) hide show
  1. package/README.en.md +40 -4
  2. package/README.ja.md +40 -4
  3. package/README.md +40 -4
  4. package/dist/{src-2V6VP4I5.js → chunk-FI56GONY.js} +32 -3103
  5. package/dist/chunk-FI56GONY.js.map +1 -0
  6. package/dist/chunk-MBPB37FR.js +1348 -0
  7. package/dist/chunk-MBPB37FR.js.map +1 -0
  8. package/dist/{chunk-7DSURTJ4.js → chunk-ZFNBUF77.js} +327 -199
  9. package/dist/chunk-ZFNBUF77.js.map +1 -0
  10. package/dist/cli-config-templates-W2LLPMB6.js +5 -0
  11. package/dist/{cli-config-templates-6AHNUGBN.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-FC3QRKDO.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-FC3QRKDO.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +920 -336
  15. package/dist/index.js.map +1 -1
  16. package/dist/metafile-esm.json +1 -0
  17. package/dist/src-DSJJXM2F.js +3849 -0
  18. package/dist/src-DSJJXM2F.js.map +1 -0
  19. package/package.json +9 -8
  20. package/templates/apps/admin/messages/ja-JP.json +38 -0
  21. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  22. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  23. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  24. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  25. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +5 -1
  26. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  27. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  28. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  29. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  30. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  31. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  32. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  33. package/templates/apps/api/package.json.hbs +1 -1
  34. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  35. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +1 -1
  36. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  37. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  38. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +1 -1
  39. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +1 -1
  40. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  41. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  42. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  43. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +1 -1
  44. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  45. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +1 -1
  46. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +19 -8
  47. package/templates/apps/fullstack/package.json.hbs +1 -1
  48. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  49. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  50. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  51. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +5 -2
  52. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  53. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  54. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  55. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  56. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  57. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  58. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  59. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  60. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  61. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  62. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  63. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +9 -4
  64. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  65. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  66. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +5 -5
  67. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  68. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +1 -1
  69. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  70. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +11 -12
  71. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  72. package/templates/apps/h5/messages/ja-JP.json +40 -0
  73. package/templates/apps/h5/src/routes/+layout.svelte.hbs +14 -3
  74. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  75. package/templates/apps/mobile-app/src/App.svelte.hbs +13 -3
  76. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  77. package/templates/apps/website/messages/ja-JP.json +32 -0
  78. package/templates/apps/website/src/routes/+layout.svelte.hbs +10 -2
  79. package/templates/base/e2e/app.spec.ts.hbs +13 -8
  80. package/templates/base/package.json.hbs +4 -1
  81. package/templates/base/playwright.config.ts +15 -4
  82. package/templates/base/src/hooks.server.ts.hbs +15 -6
  83. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  84. package/templates/base/vite.config.ts.hbs +2 -2
  85. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  86. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  87. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  88. package/templates/i18n/project.inlang/settings.json +1 -1
  89. package/templates/shared/e2e-env.ts +98 -0
  90. package/templates/shared/run-e2e.mjs +51 -12
  91. package/templates/skills/AGENTS.md +4 -0
  92. package/templates/skills/bridges/admin/AGENTS.md +5 -1
  93. package/templates/skills/bridges/api/AGENTS.md +2 -0
  94. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +4 -0
  95. package/templates/skills/bridges/h5/AGENTS.md +5 -1
  96. package/templates/skills/bridges/mobile-app/AGENTS.md +5 -1
  97. package/templates/skills/bridges/website/AGENTS.md +5 -1
  98. package/templates/skills/hai-ai/SKILL.md +8 -2
  99. package/templates/skills/hai-ai/guide.md +134 -60
  100. package/templates/skills/hai-ai/reference.md +51 -11
  101. package/templates/skills/hai-api-client/SKILL.md +2 -0
  102. package/templates/skills/hai-api-client/reference.md +17 -5
  103. package/templates/skills/hai-api-contract/SKILL.md +1 -0
  104. package/templates/skills/hai-api-contract/reference.md +4 -2
  105. package/templates/skills/hai-app-create/SKILL.md +4 -0
  106. package/templates/skills/hai-app-create/development.md +20 -4
  107. package/templates/skills/hai-app-create/reference.md +2 -1
  108. package/templates/skills/hai-app-review/SKILL.md +6 -2
  109. package/templates/skills/hai-app-review/differential.md +34 -0
  110. package/templates/skills/hai-app-tests/SKILL.md +3 -1
  111. package/templates/skills/hai-app-tests/reference.md +4 -4
  112. package/templates/skills/hai-app-tests/validation.md +10 -3
  113. package/templates/skills/hai-audit/SKILL.md +10 -8
  114. package/templates/skills/hai-audit/reference.md +3 -0
  115. package/templates/skills/hai-build/reference.md +6 -4
  116. package/templates/skills/hai-cache/SKILL.md +9 -3
  117. package/templates/skills/hai-cache/reference.md +53 -11
  118. package/templates/skills/hai-capacitor/SKILL.md +3 -1
  119. package/templates/skills/hai-capacitor/reference.md +9 -5
  120. package/templates/skills/hai-core/SKILL.md +4 -0
  121. package/templates/skills/hai-core/reference.md +25 -5
  122. package/templates/skills/hai-crypto/SKILL.md +10 -2
  123. package/templates/skills/hai-crypto/reference.md +64 -10
  124. package/templates/skills/hai-datapipe/SKILL.md +2 -0
  125. package/templates/skills/hai-datapipe/reference.md +3 -1
  126. package/templates/skills/hai-deploy/SKILL.md +9 -4
  127. package/templates/skills/hai-deploy/middleware.md +51 -37
  128. package/templates/skills/hai-iam/SKILL.md +24 -8
  129. package/templates/skills/hai-iam/reference.md +181 -154
  130. package/templates/skills/hai-kit/SKILL.md +23 -7
  131. package/templates/skills/hai-kit/reference.md +42 -24
  132. package/templates/skills/hai-payment/SKILL.md +13 -11
  133. package/templates/skills/hai-payment/reference.md +80 -41
  134. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  135. package/templates/skills/hai-reach/SKILL.md +5 -2
  136. package/templates/skills/hai-reach/reference.md +83 -22
  137. package/templates/skills/hai-reldb/SKILL.md +5 -2
  138. package/templates/skills/hai-reldb/reference.md +68 -25
  139. package/templates/skills/hai-scheduler/SKILL.md +8 -5
  140. package/templates/skills/hai-scheduler/reference.md +38 -24
  141. package/templates/skills/hai-serv/SKILL.md +23 -8
  142. package/templates/skills/hai-serv/reference.md +123 -87
  143. package/templates/skills/hai-spec-implement/SKILL.md +4 -0
  144. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  145. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  146. package/templates/skills/hai-spec-implement/templates.md +5 -0
  147. package/templates/skills/hai-spec-implement/workflow.md +2 -0
  148. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  149. package/templates/skills/hai-storage/SKILL.md +8 -2
  150. package/templates/skills/hai-storage/reference.md +64 -25
  151. package/templates/skills/hai-ui/SKILL.md +16 -2
  152. package/templates/skills/hai-ui/guide.md +3 -3
  153. package/templates/skills/hai-ui/reference.md +17 -6
  154. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  155. package/templates/skills/hai-ui-design/patterns.md +26 -0
  156. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  157. package/templates/skills/hai-ui-review/checklist.md +22 -0
  158. package/templates/skills/hai-vecdb/SKILL.md +12 -8
  159. package/templates/skills/hai-vecdb/reference.md +33 -3
  160. package/dist/chunk-7DSURTJ4.js.map +0 -1
  161. package/dist/chunk-AOQ4N657.js +0 -640
  162. package/dist/chunk-AOQ4N657.js.map +0 -1
  163. package/dist/cli-config-templates-6AHNUGBN.js +0 -5
  164. package/dist/src-2V6VP4I5.js.map +0 -1
@@ -2,6 +2,11 @@
2
2
 
3
3
  > `@h-ai/api-client` 是跨端 typed API client。公共 HTTP API 由 `@h-ai/api-contract` 定义,服务端由 `@h-ai/serv` 挂载,客户端直接调用 `apiClient.<domain>.<group>.<operation>()`。
4
4
 
5
+
6
+ 认证协议仅支持 v2:独立随机 SM4-CBC 密钥和 HMAC-SM3 密钥由 SM2 封装,先验签、再验证 MAC,最后解密 CBC。请求由客户端私钥签名,响应由所选服务端版本签名。认证字段包括 version、direction、serverKeyId、clientId、128 位随机 requestId(兼 nonce)、issuedAt、HTTP method、pathname + 原始 search 的 SM3 pathHash,以及响应 status。请求有效期 5 分钟,未来时钟偏差最多 30 秒;共享 registry 原子消费 (clientId, requestId),nonce 至少保留到 issuedAt + 6 分钟以覆盖节点偏差,存储故障拒绝请求。Redis 必须保留有效 nonce,使用 noeviction/可靠持久化策略;各节点时钟须同步。此能力防止同一传输消息重放,业务重试仍需自己的幂等键。
7
+
8
+ 空 POST 也发送空明文的认证封装;GET/HEAD 将封装放在 X-Hai-Transport 头,204/304/HEAD 等无正文响应也使用同名头承载认证封装。其它请求/响应在 JSON 正文承载封装并带 X-Encrypted: true。客户端必须关联原请求上下文和 HTTP status,缺标记、旧格式或认证失败一律 reject;明文接口只能经 excludePaths 显式排除。CORS 须允许并暴露 X-Hai-Transport。握手公钥的真实性依赖 TLS,不替代 IAM 业务授权。
9
+
5
10
  ## 使用步骤
6
11
 
7
12
  ### 1. 初始化
@@ -74,6 +79,8 @@ const upload = await apiClient.storage.presignedUrls.createUpload({ key: 'avatar
74
79
  const answer = await apiClient.ai.chats.sendMessage({ message: 'hello' })
75
80
  ```
76
81
 
82
+ 默认 `ai.chats.createCompletion({ messages, tools, tool_choice })` 只返回非流式 HaiResult JSON;不接受 stream 字段。完整 function 参数 JSON Schema、strict 与工具选择直接到模型。先检查 success,再读取助手 tool_calls;业务方授权执行工具后,以对应 tool_call_id 回传工具消息。流式接口须绑定应用自定义流契约。
83
+
77
84
  ### 3. 自定义 contract
78
85
 
79
86
  ```ts
@@ -101,7 +108,8 @@ await apiClient.close()
101
108
  | --- | --- | --- |
102
109
  | `apiClient.tokenStorage.httpOnlyCookie()` | httpOnly cookie(服务端管理) | **默认**;浏览器端推荐,refresh token 不暴露给 JS |
103
110
  | `apiClient.tokenStorage.memory()` | 内存 | SSR / Node.js 单元测试(需显式传入) |
104
- | `apiClient.tokenStorage.localStorage()` | localStorage | 非敏感场景(有 XSS 风险,生产不推荐) |
111
+
112
+ 浏览器只使用内存 access token + 服务端 httpOnly refresh Cookie;原生端注入平台安全 TokenStorage。禁止将认证凭据持久化到 Web Storage。
105
113
 
106
114
  ### httpOnly Cookie 模式
107
115
 
@@ -135,11 +143,13 @@ if (login.success) {
135
143
  | --- | --- |
136
144
  | `apiClient.init(config)` | 初始化默认 API Service client;重复初始化返回 `CONFIG_ERROR` |
137
145
  | `apiClient.close()` | 清理 client 状态 |
138
- | `apiClient.auth.setTokens(tokens)` | 写入 access token;非 httpOnly 存储会同时写入 refresh token |
139
- | `apiClient.auth.clear()` | 清理 Token |
146
+ | `apiClient.auth.setTokens(tokens)` | 使旧刷新失效并替换凭据;非 httpOnly 存储保存提供的 refresh token,缺省时清除旧值 |
147
+ | `apiClient.auth.clear()` | 使旧刷新失效并清理本客户端 Token;服务端会话/Cookie 另由退出接口撤销 |
140
148
  | `apiClient.auth.onTokenRefreshed(cb)` | 监听刷新结果 |
141
149
  | `apiClient.create(contract)` | 创建自定义 typed client |
142
- | `apiClient.tokenStorage.{memory,localStorage,httpOnlyCookie}()` | TokenStorage 工厂 |
150
+ | `apiClient.tokenStorage.{memory,httpOnlyCookie}()` | TokenStorage 工厂 |
151
+
152
+ 退出/换账号使用 `auth.clear()` / `auth.setTokens()`,不直接写 storage。方法立即取消旧刷新,并按顺序提交异步存储操作;迟到的成功/401 不会恢复旧身份、覆盖新账号或触发旧回调。该保证针对同一个客户端的认证管理器,SSR 仍须按请求隔离实例。
143
153
 
144
154
  ### Transport 配置
145
155
 
@@ -148,9 +158,11 @@ if (login.success) {
148
158
  | `transport.crypto` | 已初始化的 `@h-ai/crypto` 实例 |
149
159
  | `transport.keyExchangePath` | 相对于 `baseUrl` 的协商子路径,默认 `/_hai/key-exchange` |
150
160
 
151
- 服务端必须对应启用 `serv.createApp({ transport: { crypto } })`。
161
+ 服务端必须对应启用 `serv.createApp({ transport: { crypto, serverKeyRing, keyStore } })`。serverKeyRing 与 keyStore 仅服务端注入,生产各节点共用受管版本化 ring 和共享 registry;客户端只接收 serverKeyId/公钥并在请求密文带回版本,不得接收私钥。旧版本撤销后需销毁客户端会话并重协商,不自动重放非幂等请求。
152
162
  自动刷新会沿用 `transport.encryptedFetch`,并在 httpOnly cookie 模式下发送空请求体 + `credentials: 'include'`,由服务端从 cookie 读取 refresh token。
153
163
 
164
+ CLI 独立 mobile 模板在选择 IAM/crypto 时安装前端 crypto 并默认启用该 transport,未选择 crypto 时保留明文 API 模式。`PUBLIC_API_TRANSPORT=off` 仅用于明确匹配的明文服务;`PUBLIC_API_KEY_EXCHANGE_PATH` 可调整公开协商子路径。配置先于 client 初始化,握手失败不会回退明文。关闭顺序为 client → 本客户端初始化的 crypto,不能关闭其他调用方已有的 crypto。
165
+
154
166
  ## 常见模式
155
167
 
156
168
  - `baseUrl` 通常包含服务端 `apiPrefix`,例如 `/api/v1`。
@@ -21,6 +21,7 @@ description: "新增、组合或修改共享 HTTP 接口的路径与输入输出
21
21
  2. 在 contract 中声明 method/path 与输入输出 Schema;只导出真正跨接口或跨层复用的 Schema。
22
22
  3. 用 `apiContract.create` 组合领域;不把服务实现、私有配置或传输前缀写进 contract。
23
23
  4. 同步 serv procedures、typed client、调用方与失败/校验测试;普通业务输出按当前 HaiResult 契约。
24
+ 5. 独立 contract 包实际执行声明构建。推导需要的上游公共类型 `ContractProcedureBuilderWithInputOutput`、`ContractProcedureBuilderWithOutput`、`Schema` 已由本包转出;显式类型从公开入口 `import type`,不引用依赖内部路径。
24
25
 
25
26
  ## 按需参考
26
27
 
@@ -4,6 +4,8 @@
4
4
 
5
5
  ## 运行环境
6
6
 
7
+ 独立 contract 包发布前验证实际声明构建。`ContractProcedureBuilderWithInputOutput`、`ContractProcedureBuilderWithOutput`、`Schema` 直接复用上游公共类型,由 `@h-ai/api-contract` 类型转出,供推导声明与显式 `import type` 使用;不手写仿造类型或导入 node_modules 内部路径。
8
+
7
9
  > ✅ **契约定义包,无服务端业务依赖;Zod/oRPC schema 构造仍有运行时代码。** 可在客户端、服务端、测试环境直接引用。
8
10
 
9
11
  ## 适用场景
@@ -144,7 +146,7 @@ const contract = apiContract.create({
144
146
  | --- | --- | --- |
145
147
  | `apiContract.iam` | IAM | auth(登录/登出/OTP/刷新/注册/改密码)、users、roles、permissions Admin CRUD |
146
148
  | `apiContract.storage` | Storage | presignedUrls(上传/下载/批量)、files(元数据查询/删除) |
147
- | `apiContract.ai` | AI | chats(completion/流式)、knowledge(上传/查询) |
149
+ | `apiContract.ai` | AI | chats(非流式补全/消息/历史)、memories(召回/列表)、sessions(列表) |
148
150
  | `apiContract.payment` | Payment | orders、subscriptions |
149
151
 
150
152
  ### 内置 contract 路径示例
@@ -162,7 +164,7 @@ myContract.iam.roles.create // POST /iam/roles
162
164
  myContract.storage.presignedUrls.createUpload // POST /storage/presigned-urls/upload
163
165
 
164
166
  // AI
165
- myContract.ai.chats.createCompletion // POST /ai/chats/completion
167
+ myContract.ai.chats.createCompletion // POST /ai/chats/completions,非流式 JSON
166
168
  ```
167
169
 
168
170
  ### `apiContract.haiResultSchema(dataSchema)` — 标准输出包装
@@ -17,6 +17,8 @@ description: "在 hai 应用中新增、修改或修复功能,按需求复用
17
17
 
18
18
  ## 使用路径
19
19
 
20
+ 范围、验收、复用依据和证据按 [工作交接契约](../hai-spec-implement/handoff.md) 贯穿实现、测试和审查;小改动使用简短记录,文档驱动任务沿既有任务记录续跑。
21
+
20
22
  业务以用户当前明确要求及其指定文档为准;模板规则、生成代码与测试只描述初始方案。需求改变角色、流程、限额或架构时,在已有授权范围内同步调整冲突实现及过期 AGENTS/skills/README,不要求把需求改回模板。具体判定见 [development.md](development.md#需求与模板的关系)。
21
23
 
22
24
  从需求/设计文档开始生成项目或功能前,使用 [hai-spec-review](../hai-spec-review/SKILL.md) 检查文档是否足以确定实现;修正影响行为的缺项、歧义和技术错误后再进入生成。普通小改动按本次范围确认规则,不要求重审无关文档。
@@ -32,6 +34,8 @@ description: "在 hai 应用中新增、修改或修复功能,按需求复用
32
34
 
33
35
  ## 按需参考
34
36
 
37
+ 界面新建或明显改变信息结构时先用已交付的 hai-ui-design 确定页面模式、状态和组件映射;使用 hai-ui 的当前版本契约实现,以 hai-ui-review 的真实页面检查收尾。窄范围修复直接复用既有设计。界面方案与最终实现的差异需回写适用设计记录。
38
+
35
39
  新增新手引导或帮助说明时读取 [UI 引导指南](../hai-ui/guide.md),复用 fullstack 默认的共享 guide-store、locale 和稳定 data-guide 锚点。
36
40
 
37
41
  按任务读取 [reference.md](reference.md) 的对应小节;签名以当前源码或安装版本的公开类型为准,不默认加载全部资料。示例中的业务变量由应用提供。
@@ -14,6 +14,7 @@
14
14
 
15
15
  - 读取当前 `AGENTS.md`、README、`package.json`、lockfile、初始化入口和已有相似功能;用 `rg` 查实现、引用、测试。保留已有改动,沿用项目分层、命名、ESLint、i18n 与测试入口。
16
16
  - 确认架构:SvelteKit 使用 `kit` 和文件路由;API workspace 使用 contract/service;fullstack 使用 contract/serv/shared 与所选前端;Vite SPA/原生端通过 typed API 客户端调用服务。不要把服务端目录、DB、密钥或 Node 初始化复制到浏览器。
17
+ - 独立 mobile 模板选择 IAM/crypto 时包含前端 crypto,默认启用 typed client 加密 transport;无 crypto 的客户端保留明确的明文 API 模式。只有匹配的明文服务才设置 `PUBLIC_API_TRANSPORT=off`;`PUBLIC_API_KEY_EXCHANGE_PATH` 只覆盖公开协商子路径。握手失败不降级,关闭时先销毁 client transport,再关闭自身初始化的 crypto;服务端 ring 私钥不进入客户端。
17
18
  - 从所属 workspace 的依赖与 lockfile 确认 `@h-ai/*` 实际解析版本和 npm/local 模式。模板 skill 提供导航,**当前安装包的 `package.json.exports`、公开 `.d.ts` 与随包文档决定可用 API**;本地框架源码只在版本匹配或显式联调时作实现证据。
18
19
  - 若 skill 与安装版本不符,以公开类型和实际行为核验。先使用已支持能力;任务要求升级时按 [hai-framework-sync](../hai-framework-sync/SKILL.md) 发现目标、比较差异并完成迁移验证。不能直接复制未发布源码的 API,也不能为绕过差异添加虚构兼容层。
19
20
 
@@ -36,6 +37,8 @@
36
37
 
37
38
  YAML 使用 `${HAI_DB_TYPE:sqlite}` 等显式引用,冒号后是未配置时的本地默认值。应用仍通过 `core.config.validate(name, ModuleConfigSchema)` 和模块初始化校验类型、枚举、范围和必填项;插值本身不能代替校验。只开启实际使用的模块,未启用模块的整份 YAML 保留注释。
38
39
 
40
+ 生产禁止使用进程内或节点本地状态。CLI 的 `.env.prod` 按已启用模块选择 PostgreSQL(保留明确选择的 MySQL)、Redis、S3、Qdrant,默认 Compose DNS 为 `db/cache/storage/vecdb`,容器外运行用私有环境覆盖连接地址。必须私有注入密码、S3/模型凭据及受保护 Qdrant 的 API Key。AI 硬依赖 reldb+vecdb,按数据库→向量库→AI 初始化,失败逆序释放并检查关闭结果,清理失败保留重试义务。生产 Reach 必须私有注入真实 `HAI_REACH_PROVIDERS` JSON;公共空数组有意拒绝启动,不能沿用 console。`hai add ai` 补包与配置,但现有业务入口仍须接入相同生命周期。
41
+
39
42
  #### 命名和类型
40
43
 
41
44
  - `HAI_<配置名>_<字段路径>`:`_db.yml` 的 `type` 对应 `HAI_DB_TYPE`;`_ai.yml` 的 `llm.apiKey` 对应 `HAI_AI_LLM_APIKEY`。camelCase 不拆词,数组使用从零开始的索引,如 `HAI_AI_AUDIO_MODELS_0_BASEURL`。
@@ -53,6 +56,8 @@ YAML 使用 `${HAI_DB_TYPE:sqlite}` 等显式引用,冒号后是未配置时
53
56
 
54
57
  所有入口优先级一致:**进程环境 > `.env.local` > `.env.<dev|test|prod>` > `.env`**。Vite 配置在 Node 阶段通过 `core.config.loadEnvFiles({ mode, envDir })` 读取,检查 `success` 后注入进程;`development`/`production` 分别映射到 `dev`/`prod`。Node 服务按 `.env`、环境文件、`.env.local` 的顺序传入 `--env-file`,后者覆盖前者,已有进程环境最高。`core.init` 只读取已注入的变量,不自行读取 dotenv。
55
58
 
59
+ 托管 `test:e2e` 是明确的隔离测试入口:服务子进程使用本轮绝对数据目录和完整模块配置,不继承 ambient 模块变量,也不读取用户 `.env.local`。Svelte 仅在 `HAI_E2E=1` 的测试分支跳过 dotenv;API 测试直接执行已构建产物。普通开发与生产的上述优先级和共享后端守卫保持不变,transport 使用测试独立可信 ring 与实际 registry。
60
+
56
61
  `prod` 是文件模式名,对应 `HAI_CORE_ENV=production`;环境枚举为 `development | test | production | staging`。SvelteKit adapter-node 产物使用 `node --env-file=.env --env-file=.env.prod --env-file-if-exists=.env.local build/index.js`。文件路径相对进程工作目录,公共文件随项目提交;容器通过部署平台或 `--env-file` 注入目标环境值。浏览器只接收公开白名单,构建产物和镜像不包含 `.env.local`。
57
62
 
58
63
  Fullstack 的环境文件在工程根;service 脚本通过 `../../.env*` 加载,Web/App/Desktop 的 Vite 自动 dotenv 加载关闭(`envDir: false`),开发和预览由 `core.config.loadEnvFiles` 从工程根读取,构建不读取部署环境文件。API workspace 的环境文件在 `apps/<name>-service/`。独立应用在各自根目录加载。静态 Web 通过 `runtime-config.js` 获取公开地址,部署环境覆盖无需重建镜像;原生包在复制静态资源前配置该入口。修改编译进客户端的默认值时仍需重新构建;fullstack 的公开应用元数据由 service `app.info` 返回。
@@ -68,13 +73,12 @@ Fullstack 的环境文件在工程根;service 脚本通过 `../../.env*` 加
68
73
  | 变量 | 说明与取值约束 |
69
74
  | ----------------------------------------------------------------- | -------------------------------------------------------------------------------------- |
70
75
  | `HAI_CORE_ENV` | `development \| test \| production \| staging` |
71
- | `HAI_CORE_DEBUG` | `true \| false` |
72
76
  | `HAI_CORE_LOGGING_LEVEL` | `trace \| debug \| info \| warn \| error \| fatal` |
73
77
  | `HAI_CORE_LOGGING_FORMAT` | `json \| pretty` |
74
78
  | `HAI_DB_TYPE` | `sqlite \| postgresql \| mysql` |
75
79
  | `HAI_DB_DATABASE` | SQLite 文件路径或 `:memory:`;网络数据库为数据库名 |
76
80
  | `HAI_DB_HOST` / `HAI_DB_PORT` / `HAI_DB_USER` / `HAI_DB_PASSWORD` | 主机、整数端口 1–65535、用户名、敏感密码;PostgreSQL 常用 5432,MySQL 常用 3306 |
77
- | `HAI_DB_URL` / `HAI_DB_SSL` | 非空连接 URL 优先于拆分字段;TLS 为布尔值、require/prefer/allow/disable 或 TLS 对象 |
81
+ | `HAI_DB_URL` / `HAI_DB_SSL` | 非空连接 URL 优先于拆分字段;MySQL TLS 仅 false 或严格对象({} 默认验证证书链和 DNS 身份);PostgreSQL 接受其布尔/字符串模式或对象 |
78
82
  | `HAI_CACHE_TYPE` / `HAI_CACHE_DB` | `memory \| redis`;DB 为整数 0–15;memory 仅单进程使用 |
79
83
  | `HAI_STORAGE_TYPE` | `local \| s3`;local 要求 root,s3 要求 bucket、region、accessKeyId、secretAccessKey |
80
84
  | `HAI_SERV_SERVER_HOST` / `HAI_SERV_SERVER_PORT` | HTTP 主机和整数端口 1–65535;部署平台 HOST/PORT 可覆盖 |
@@ -106,7 +110,7 @@ HAI_DB_USER=my_app
106
110
  HAI_DB_SSL=require
107
111
  ```
108
112
 
109
- 切换到 MySQL 时相应设置 `HAI_DB_TYPE=mysql`、端口和凭据。确保项目已安装所选 Provider 的驱动;数据库数据迁移另行执行,切换配置不会搬迁数据。缓存切到 Redis、存储切到 S3 时同样填写对应连接字段。
113
+ 切换到 MySQL 时相应设置 `HAI_DB_TYPE=mysql`、端口和凭据,并把 `HAI_DB_SSL=require` 改为严格 TLS 对象,例如 `HAI_DB_SSL='{}'`(默认校验证书链和 DNS 身份);自签 CA 使用 `ca` PEM,mTLS 的 `cert` / `key` PEM 必须成对。私钥仅放被忽略的 `.env.local` 或目标进程环境。确保项目已安装所选 Provider 的驱动;数据库数据迁移另行执行,切换配置不会搬迁数据。缓存切到 Redis、存储切到 S3 时同样填写对应连接字段。
110
114
 
111
115
  ## CLI 安装与命令
112
116
 
@@ -123,6 +127,12 @@ hai create my-app
123
127
 
124
128
  已有项目先进入项目根目录,再按需要执行 `hai add <module>`、`hai init` 或 `hai generate <type> <name>`。已安装 CLI 时,相同安装命令可更新到当前发布版本;需要指定版本时使用 `@h-ai/cli@<version>`。命令和模板以实际安装的 CLI 为准,不能把未发布的框架源码能力当作已安装包能力。
125
129
 
130
+ `hai generate page` 只写 SvelteKit 文件路由;`api` 按目标应用生成 SvelteKit 端点或 serv 的共享 contract/procedure 并接入已有聚合入口。API/fullstack 根自动选择唯一后端,多个候选用 `--workspace <包名或相对路径>`,也可进入目标 workspace。纯 Vite 不生成虚假文件路由;serv 不接受 `--output`,SvelteKit 输出限定 `src/routes` 内,定制 `kit.files` 时拒绝猜测;SvelteKit API 目标须已安装 `@h-ai/kit` 与 `zod`。不要删除模板 `@hai-generator` 聚合标记;自定义入口没有标记时手动沿现有架构接入。生成前统一校验路径和覆盖冲突,失败回滚本次修改;`--force` 不能覆盖已有业务领域注册。
131
+
132
+ CLI 用户消息通过现有中英字典输出。初始语言按非空的 `HAI_CORE_DEFAULTLOCALE` → `LC_ALL` → `LC_MESSAGES` → `LANG` 选择,`en`/`zh` 地区变体映射到现有语言,`C`/`POSIX` 为英文;未知语言保留 core 当前语言(默认中文)。CLI 内部命令响应 `core.i18n.setGlobalLocale('en-US')` 的同进程切换;包入口仍为命令行入口,不提供库调用 API。`hai generate` 省略类型/名称会交互,取消不写文件;生成源码的中文注释与用户名称不翻译。
133
+
134
+ `create` / `add` / `init` 的校验、选择项、模块/模板名称与描述、错误前缀及完成提示也使用当前 CLI locale;目录标签延迟求值。CLI 语言不修改生成应用的 `core.defaultLocale`。生成的中文 README、YAML/源码注释与外部工具原始错误保留原样;不要把这些正文机械替换成终端翻译。
135
+
126
136
  ## 将需求映射到现有能力
127
137
 
128
138
  应用元信息只有一份配置来源:名称、版本、环境、默认语言与日志设置来自 `_core.yml`,使用 `core.config` 与 `core.logger`;Node 启动时不通过固定 `logging` 覆盖 YAML。SvelteKit 服务端 layout 和 fullstack `app.info` 仅公开必要元信息,不能下发整个 core 或模块配置。独立 Vite 客户端经 Node/core 加载 YAML 默认值,Web 部署通过 `runtime-config.js` 应用公开白名单覆盖;只修改部署覆盖无需重建,修改构建默认值则须重新构建。环境覆盖使用 `HAI_CORE_NAME`、`HAI_CORE_VERSION`、`HAI_CORE_ENV`、`HAI_CORE_LOGGING_LEVEL` 等既有规则;容器明确配置生产环境,不能用另一个状态来源代替 `core.env`。
@@ -174,7 +184,7 @@ Fullstack 创建时在被 Git 与容器构建忽略的根 `.env.local` 写入独
174
184
  | HaiResult 与错误 | HaiResult 型业务 API 先判断 `success`,服务层保留错误;HTTP 边界沿实际包络/状态契约并隐藏内部敏感细节。显式抛错、流迭代与 SvelteKit 控制流按各自契约 | 依赖失败不回退为空列表/成功消息;流失败与业务状态失败有反馈,用户输入得到保留 |
175
185
  | 初始化与清理 | 只对有生命周期的模块按依赖 init → use → close;检查每次结果,关键依赖失败停止启动,清理已创建资源,关闭反序 | 初始化失败不能继续执行业务;关闭/取消后连接、监听器与任务退出 |
176
186
  | 写入与重试 | 多步一致性用事务或明确补偿;重试/回调/任务定义幂等;跨节点业务状态使用共享存储 | 中途失败无不一致写入;重复请求/回调不产生重复副作用 |
177
- | 风格与可维护性 | 禁止 `any`、无说明的 `as unknown as T`、`console.log`、硬编码密钥;注释中文、日志英文且脱敏;文案使用现有 i18n 并同步中英文;遵循项目实际 lint 配置 | typecheck/lint 通过;人工核对脱敏、文本、分层与不受 lint 覆盖的规则 |
187
+ | 风格与可维护性 | 禁止 `any`、无说明的 `as unknown as T`、`console.log`、硬编码密钥;注释中文、日志英文且脱敏;文案使用现有 i18n 并同步中英日文;遵循项目实际 lint 配置 | typecheck/lint 通过;人工核对脱敏、文本、分层与不受 lint 覆盖的规则 |
178
188
 
179
189
  不把所有函数改成 HaiResult:纯工具、getter、工厂、客户端/流及控制流按实际签名。不把所有模块加上 init/close。应用服务根据已有业务契约返回 HaiResult,HTTP/UI 层做适当转换。
180
190
 
@@ -215,3 +225,9 @@ export function pageOffset(query: PageQuery): number {
215
225
  - 从当前 `package.json` 选择实际脚本,优先 workspace filter,PowerShell 使用 `pnpm.cmd`。多项门禁按 [分轮验证与集中修复](../hai-app-tests/validation.md) 先脚本化收集结果和日志,再集中修复、复测失败及受影响项;普通失败不立即从头重跑。不能调用框架仓库私有命令替代应用检查。
216
226
  - typecheck/lint 只覆盖部分质量规则;权限、事务、降级、真实 provider、浏览器、容器和原生交付各自需要适用行为证据。缺环境或密钥时报告未验证,不能用 mock 冒充真实集成。
217
227
  - 完成说明:实际复用能力与自定义缺口、修改路径、已执行命令/行为结果、同步的 contract/调用方/配置/i18n/文档、未验证项及原因。无需无关模块的“覆盖率”清单。
228
+
229
+ `_core.yml` 只接受 name/version/env/defaultLocale 元数据与 logging;不提供 debug、全局ID设置、features或supportedLocales。业务开关放app.yml并用业务Schema校验;语言列表按实际翻译工程维护,日志用logging.level。core默认语言字段不会自动注册翻译。Logger导入保留LANG/LC_ALL/PYTHONIOENCODING,终端语言优先级不能被编码初始化覆盖。
230
+
231
+ 框架环境目录从真实入口生成;JS/TS(含 Svelte script)中的 JSDoc 示例不构成新运行变量。Schema 字段注释仍生成用途说明,YAML/模板可选注释和字符串内生成代码引用继续保留;不能只在示例里写变量就声称已被实现消费。
232
+
233
+ 公开导入以安装版本的 package.json.exports 为准:只使用已声明且有真实产物的根/子路径,不导入 src/dist 或内部 Provider/Deps。条件入口可以只提供配置和类型;IAM 浏览器入口不含服务端 iam 对象。Svelte 默认组件和明确的 selective/type-only 导出均合法,不能为形式统一扩大公共 API。
@@ -80,6 +80,7 @@ src/
80
80
  users/+server.ts
81
81
  messages/ # i18n 消息文件
82
82
  en-US.json
83
+ ja-JP.json
83
84
  zh-CN.json
84
85
  config/ # 模块配置文件
85
86
  _core.yml
@@ -300,7 +301,7 @@ interface CreateArticleInput {
300
301
  </div>
301
302
  ```
302
303
 
303
- `Article` 放在客户端可用的共享类型位置,`article_edit` 在中英文 messages 中新增后由项目生成;实际已安装 `@h-ai/ui` 有适用 Card/Button 时使用其公开组件和 Props,本例只展示业务 props、事件与文案边界。
304
+ `Article` 放在客户端可用的共享类型位置,`article_edit` 在中英日文 messages 中新增后由项目生成;实际已安装 `@h-ai/ui` 有适用 Card/Button 时使用其公开组件和 Props,本例只展示业务 props、事件与文案边界。
304
305
 
305
306
  ### 组件规则
306
307
 
@@ -17,6 +17,8 @@ description: "审查 hai 应用的框架复用、安全边界、返回契约、
17
17
 
18
18
  ## 审查路径
19
19
 
20
+ 对代码差异读取 [差异审查](differential.md):固定 base/head,先核对需求符合性,再按真实风险检查历史约束、直接与关键间接调用方。完整应用审查使用同样的需求追踪与发现复核,说明实际覆盖范围。发现、修复和验收沿 [工作交接契约](../hai-spec-implement/handoff.md) 记录。
21
+
20
22
  1. 读取当前项目入口规则、用户指定需求、package scripts、lockfile 与差异,确认 SvelteKit、API workspace、fullstack 或 Vite/原生架构。业务以当前明确需求为准;模板角色、流程、限额和生成代码是初始实现,不能据此否定新需求或要求恢复已被替代的行为。
21
23
  2. 按 [development.md](../hai-app-create/development.md) 对需求核对实际框架能力、当前安装版本及适用安全边界;只读所涉及模块 skill、公开类型和相关调用方。
22
24
  3. 沿真实路径检查输入 → 身份/资源权限 → 服务/副作用 → 结果 → UI。先报告能导致安全或行为错误的问题,再检查风格与维护性。
@@ -55,17 +57,19 @@ typecheck/lint 不能证明越权、租户隔离、事务、错误状态或资
55
57
  - 禁止 `any`、无说明的 `as unknown as T`、`console.log`、硬编码密钥;外部 `unknown` 先校验/缩窄。必要第三方断言有真实依据。
56
58
  - 遵循当前 ESLint、TypeScript、目录与命名规则,函数返回类型按真实契约标注;不把所有返回类型都强制成 HaiResult。
57
59
  - 注释中文,结构化日志英文并脱敏。读操作用 debug,写操作成功用 info,失败按业务/系统性质用 warn/error;避免记录整份请求、凭据或未经脱敏的 error/context。
58
- - 用户可见文案沿现有 i18n 入口,同步中英文;包括错误、空态、降级、校验和状态文案。禁止手改 Paraglide 生成文件;框架组件内置翻译沿公开 Props 使用。
60
+ - 用户可见文案沿现有 i18n 入口,同步中英日文;包括错误、空态、降级、校验和状态文案。禁止手改 Paraglide 生成文件;框架组件内置翻译沿公开 Props 使用。
59
61
  - 业务逻辑不塞入 UI/hook;优先 early return、按职责拆分复杂函数。遵守实际 lint 的复杂度/长度限制,不为了数字拆出无意义转发链。
60
62
  - 循环串行/并发按依赖、事务、限流与资源限制选择;列表分页/流式处理,避免热路径同步 I/O、重复连接和无界并发。
61
63
 
62
64
  ## 交互与行为验证
63
65
 
66
+ 界面设计与真实交互的详细审查由已交付的 hai-ui-review 提供;应用审查仍负责端到端业务与服务端权限,复用已有发现和证据,避免重复执行相同检查。
67
+
64
68
  涉及 UI 时沿真实用户路径走查,确认适用的:
65
69
 
66
70
  - Svelte 5 Runes、props 事件、SSR/浏览器边界及项目样式系统;优先复用 `@h-ai/ui` 的公开组件。
67
71
  - Loading/Empty/Error/Success 四态、表单校验/错误焦点、长任务进度、失败后输入恢复与重试。
68
- - 桌面/移动布局、长文本与中英文展开、主题对比、键盘/触摸/IME、焦点与可访问性;危险操作有符合产品目标的确认/撤销。
72
+ - 桌面/移动布局、长文本与中英日文展开、主题对比、键盘/触摸/IME、焦点与可访问性;危险操作有符合产品目标的确认/撤销。
69
73
  - 并发操作、网络失败、权限变化与返回导航不导致假成功、丢失输入或死路。
70
74
 
71
75
  新增/改变行为有对应的有效单元/集成/E2E 用例;关键接口覆盖正常、失败及适用权限/边界路径。已有代码是否遵循 TDD 不能仅从最终 diff 推断,需当时执行证据;测试数量与机械打勾不代替行为验证。
@@ -0,0 +1,34 @@
1
+ # 差异审查
2
+
3
+ 应用改动自查、PR/提交/分支审查按需读取。本方法借鉴 [Trail of Bits differential-review](https://github.com/trailofbits/skills/tree/main/plugins/differential-review),按实际风险选择审查深度,不以 diff 行数决定安全性。
4
+
5
+ ## 1. 固定比较对象
6
+
7
+ PR 使用其实际 base/head SHA,分支使用已确认集成分支与目标分支的 merge-base。记录比较命令和两端 SHA;区分提交间差异、staged、unstaged 和未跟踪文件。审查期间 head 变化时,补审新增差异并重新核验受影响结论,不沿用旧行号声称已审查当前版本。
8
+
9
+ 先列 `git diff --name-status --find-renames <base> <head>`、统计和完整 diff,再按需读取文件上下文、公开类型、测试、配置及调用方。不能仅凭截断 diff 做全量结论。无 Git、远程权限不足或缺少 base 时,说明实际可读范围,继续可进行的文件级审查。
10
+
11
+ ## 2. 先找可能造成较大后果的变化
12
+
13
+ | 变化 | 必查内容 |
14
+ | --- | --- |
15
+ | 登录、鉴权、租户、资源 ID、输入校验 | 谁能调用、能操作谁的数据、校验移除后替代约束是否真实存在 |
16
+ | 支付、上传、外部请求、AI 工具、任务 | 验签、目的地/大小限制、幂等、未知结果、重试/取消和外部副作用 |
17
+ | 状态、事务、迁移、共享缓存 | 并发/跨节点约束、失败回滚、存量数据和兼容调用方 |
18
+ | 公共 API、配置、默认值、导出、模板 | 正反向调用、公开类型、目标平台和生成应用是否同步 |
19
+ | UI 和内容渲染 | 服务端权限、HTML/URL 边界、交互丢失、键盘、焦点和语言;不能自动判为低风险 |
20
+ | CI、依赖、部署脚本 | 按 [hai-ci](../hai-ci/SKILL.md) 核对执行来源、权限、密钥与发布条件 |
21
+
22
+ 对高风险符号用 `rg` 查直接调用方及关键间接路径,列出实际依赖范围;动态注册、远端消费者无法确定时说明限制,引用数量不代表完整影响。对删除的校验、锁、事务和失败处理读相关 `git log` / `git blame`,判断是否移除了此前修复的约束。没有替代实现的“只是重构”仍需验证行为。
23
+
24
+ ## 3. 两次核对
25
+
26
+ 第一次核对需求符合性:逐条检查当前目标验收,区分完整实现、已有满足、未实现和证据不足。第二次沿输入 → 身份/资源 → 服务/副作用 → 结果 → UI 检查正确性与集成。只读当前风险需要的模块资料,普通改动不升级为无关的全面安全审计。
27
+
28
+ 把可疑点转为具体场景,例如“普通用户提交其他租户的资源 ID,更新语句未包含租户条件”,再核对上游守卫、服务和数据库约束能否实际阻止它。验证新/改变行为对应的成功与失败用例;缺少测试表示验证缺口,不直接证明漏洞,也不能仅因此提高业务问题严重程度。
29
+
30
+ ## 4. 报告与复核
31
+
32
+ 采用 [交接契约](../hai-spec-implement/handoff.md) 的发现格式。每条引用当前差异行及必要上下文,说明改动引入还是既有问题、触发条件、具体后果、证据充分程度、最小修正和验证方式。排除不成立的线索,不将已否定建议继续列为风险。
33
+
34
+ 结尾记录已覆盖文件/路径、未读或被截断部分、未执行验证和原因、需要交付前解决的事项。无发现只说明本次范围未确认问题,不等同于全系统无缺陷或 CI 已通过。
@@ -17,12 +17,14 @@ description: "为 hai 应用编写或修复 Vitest/Playwright 测试,组织分
17
17
 
18
18
  ## 使用路径
19
19
 
20
+ 先复用 [工作交接契约](../hai-spec-implement/handoff.md) 中的验收编号和本轮范围。结果关联提交与未提交差异、命令/操作、依赖与环境、退出码和证据位置;源码/配置/依赖或环境变化后按影响使旧结果失效并复测。测试通过只是相应验收的证据,整体验收仍逐项核对需求。
21
+
20
22
  - 先选单元/集成/浏览器层,复用测试 fixture,验证用户可观察结果。
21
23
  - 验收以用户当前明确要求和指定业务文档为准;模板测试不是不可变需求。规则已改变时同步更新或替换过期断言,记录新需求依据并覆盖新的授权/失败边界;保留仍适用的回归,不能用“保持旧测试通过”强留冲突业务。
22
24
  - 按 [development.md](../hai-app-create/development.md) 的真实行为与安全边界选测试;新增/改变业务行为、缺陷修复有目标用例,纯文案/格式/文档及无行为变化的可逆低影响修改不补镜像测试。
23
25
  - 不要求每个功能同时新增单元与 E2E;复用已有用例,选择能验证变化的最小适用层,关键跨层用户流程仍需端到端证据。
24
26
  - 新用例已通过时先检查是否已有能力;不能为达成 Red 人为破坏旧测试。
25
- - 执行多项门禁前读取 [分轮验证与集中修复](validation.md):一轮先脚本化收集所有可执行检查及日志,普通失败不立即改代码;汇总去重后集中修复,再复测失败和受影响项。只有真实前置阻塞才暂停依赖检查;最终必需门禁均须对最终代码有效通过。小范围 TDD Red → Green 保留。
27
+ - 执行多项门禁前读取 [分轮验证与集中修复](validation.md):一轮先脚本化收集所有可执行检查及日志,普通失败不立即改代码;汇总去重后集中修复,先定向复测失败、解除阻塞和直接受影响项,全部通过后再扩大验收。定向回归未通过时不重跑整套门禁。只有真实前置阻塞才暂停依赖检查;最终必需门禁均须对最终代码有效通过。小范围 TDD Red → Green 保留。
26
28
  - 外部服务未运行必须报告;mock/Testcontainers/浏览器/真实付费 Provider 的结果分开表述。
27
29
 
28
30
  ## 按需参考
@@ -52,7 +52,7 @@
52
52
 
53
53
  1. 参照 `hai-app-review` 技能审查代码规范
54
54
  2. 重构后执行受影响测试与必要回归,确认已有行为保持通过
55
- 3. 多项门禁按 [分轮验证与集中修复](validation.md) 收集一轮结果,汇总后修复、再验证;不要把目标用例的小范围 TDD 扩大为每修一处就从头运行全量门禁。PowerShell 使用 `pnpm.cmd`
55
+ 3. 多项门禁按 [分轮验证与集中修复](validation.md) 收集一轮结果,汇总后集中修复;先定向确认失败和直接受影响项均已解决,再执行较完整验收。定向回归未通过时继续小范围修复,不从头重复全量门禁。PowerShell 使用 `pnpm.cmd`
56
56
 
57
57
  ## 测试目录约定
58
58
 
@@ -210,16 +210,16 @@ function resolvePortFromBaseUrl(urlString: string, fallbackPort: string): string
210
210
 
211
211
  export default defineConfig({
212
212
  testDir: './e2e',
213
- fullyParallel: false,
213
+ fullyParallel: true,
214
214
  forbidOnly: !!process.env.CI,
215
- retries: process.env.CI ? 2 : 0,
215
+ retries: 0,
216
216
  workers: 2,
217
217
  reporter: 'list',
218
218
  timeout: 30_000,
219
219
  use: {
220
220
  baseURL,
221
221
  channel: 'chrome',
222
- trace: 'on-first-retry',
222
+ trace: 'retain-on-failure',
223
223
  screenshot: 'only-on-failure',
224
224
  },
225
225
  webServer: {
@@ -1,6 +1,6 @@
1
1
  # 分轮验证与集中修复
2
2
 
3
- 适用于开发、审查修复、升级后的质量门禁与整体验收。默认执行“收集一轮 → 归并问题 → 集中修复 → 再验证”,不要遇到一个普通失败就修改代码并从头重跑。新增行为的目标用例仍可做小范围 TDD Red → Green;它不要求提前运行整套门禁。
3
+ 适用于开发、审查修复、升级后的质量门禁与整体验收。默认执行“收集一轮 → 归并问题 → 集中修复 → 定向回归通过 → 较完整验收”,不要遇到一个普通失败就修改代码并从头重跑。新增行为的目标用例仍可做小范围 TDD Red → Green;它不要求提前运行整套门禁。
4
4
 
5
5
  ## 1. 确定本轮检查
6
6
 
@@ -20,12 +20,19 @@
20
20
 
21
21
  - 本轮可执行检查结束后,把问题按根因去重,记录位置、证据、影响、修复项及关联门禁。上游故障造成的连锁失败合并处理;区分本次引入、原有问题和环境问题。
22
22
  - 在已有授权范围内集中修复本次范围的问题,保留仍有效的断言与回归。不能靠删断言、跳过用例、放宽超时或关闭规则换绿灯。环境故障先保存证据、确认恢复条件,再定向重试;不反复重跑到偶然通过。
23
+ - 下载超时先区分 registry、DNS、代理等网络前提与实际编译/业务错误。复用包管理器的有界重试、可信镜像和缓存,保留总时限与每次结果;只对可安全重复的瞬时网络操作重试,不重试断言或掩盖业务失败。超时/取消后确认本轮进程树及已登记资源回收,再重试或启动冲突任务。
23
24
  - 新发现的独立问题加入同一清单;修复期间的小范围诊断不触发从头全量重跑,也不能替代下一轮必要验收。
24
25
 
25
- ## 4. 下一轮与完成条件
26
+ ## 4. 先定向回归,再扩大验收
26
27
 
27
- - 修复后开启新一轮,先复测失败项、被阻塞项及受改动影响的检查和消费者;按同样方式收集完再修复。有影响范围不明、共享配置/依赖改变或用户要求时,扩大到全量门禁。
28
+ - 集中修复后,先用最小适用范围验证原失败、已解除的阻塞及直接受影响的消费者;保留必要构建/服务前置。定向回归仍失败时继续归并和修复,只重跑失败及受新改动影响的项,暂不启动较完整门禁。
29
+ - 相关问题均已验证解决后,再按影响扩大到工作区或完整用户流程验收;影响范围不明、共享配置/依赖改变或用户要求全量时,执行全量门禁。扩大验收发现新问题时,仍回到“收集 → 集中修复 → 定向回归通过 → 扩大验收”,不要每改一处就重复整套流程。
28
30
  - 只有源码、依赖、配置、工具、环境及所用产物等输入均未改变的已通过项才可复用,并注明来源轮次或缓存;输入变化使相关结果失效。最终所有必需检查必须对最终代码仍有效,缺项不能称为完成。真实部署、Provider、浏览器或原生验收不能只引用静态检查/构建缓存。
29
31
  - 保留原有 CI/发布门禁的失败阻断;问题收集时继续运行不代表允许失败提交发布或忽略 required checks。
30
32
  - 每轮都在 finally/teardown 停止本轮进程、清理本轮临时工程/容器/镜像/数据;确认退出和资源释放后再运行冲突任务。日志、截图与报告保留到问题分析和交付结束,再按项目约定清理,不覆盖失败证据、不提交验收产物、不删除用户资源。
33
+ - 部署夹具先检查固定公开基础镜像,仅确认缺失才拉取;成功 create 先登记完整 ID,再在私有 PID namespace 内异步准备 APT/pip/sshd。所有准备 exec 转交同一取消信号,等待本地客户端 close 后按该 ID 停止全树并核验消失。总准备 deadline 早于 hook 并预留回收预算;公共缓存不删,私有 tag 回收前复核内容 ID/owner。未取得可信 create 回执或测试进程被强制终止时记录未知,不能按名称或 label 认领资源。
34
+ - 浏览器执行器默认将每轮、每组的证据分别放入忽略的 `test-results/run-*/group-*`;只清理本轮临时数据库/上传数据。显式 `HAI_AUTH_EVIDENCE_DIR`、`--output` 和 Playwright HTML 输出目录会保留,取消也不删除已有报告。不要复制私有环境、密钥或业务数据库到证据目录。
35
+ - 经托管 `test:e2e` 入口为服务子进程提供绝对 `HAI_E2E_DATA_ROOT` 和显式测试后端,隔离继承的 `HAI_*` / `PUBLIC_*` / `VITE_*` 与用户 `.env.local`。仅本轮测试启用 `HAI_E2E=1`;普通开发与生产的 dotenv 加载、共享后端守卫不放宽。启用 transport 的验收使用本轮可信 ring 和 registry,不能关闭加密掩盖客户端契约问题。
31
36
  - 最终报告各项门禁状态、执行/复用范围、修复结果和未验证原因;mock、容器、真实服务及原生结果分别说明。只陈述实际证据,不以“脚本执行完了”代表验证通过。
37
+
38
+ 公开 API 变更需验证 package.exports 实际入口与配套声明,核对根/子路径真实导入和浏览器打包;不使用 src/dist 深层导入绕过缺失公开产物。Svelte 默认组件、明确 selective/type-only 导出与 client/server 分隔按实际契约验收,不把内部 Provider/Deps 扩成公共 API。
@@ -7,21 +7,23 @@ description: "接入 @h-ai/audit 记录业务操作,查询分页日志与统
7
7
 
8
8
  ## 能力契约
9
9
 
10
- | 项目 | 契约 |
11
- | --- | --- |
12
- | 能力 | 使用 @h-ai/audit 记录、查询、统计和清理操作审计 |
13
- | 适用场景 | 业务操作留痕与审计记录检索 |
14
- | 输入 | AuditInitConfigInput、已初始化 reldb、审计事件与查询条件 |
15
- | 输出 | 记录/查询/统计的 HaiResult |
16
- | 限制 | 仅服务端;审计内容需脱敏,保留期和清理范围由应用策略决定。 |
10
+ | 项目 | 契约 |
11
+ | -------- | ---------------------------------------------------------- |
12
+ | 能力 | 使用 @h-ai/audit 记录、查询、统计和清理操作审计 |
13
+ | 适用场景 | 业务操作留痕与审计记录检索 |
14
+ | 输入 | AuditInitConfigInput、已初始化 reldb、审计事件与查询条件 |
15
+ | 输出 | 记录/查询/统计的 HaiResult |
16
+ | 限制 | 仅服务端;审计内容需脱敏,保留期和清理范围由应用策略决定。 |
17
17
 
18
18
  ## 使用路径
19
19
 
20
- 1. 确认已初始化 reldb,校验 `AuditInitConfigInput` 后初始化 audit;每步失败停止后续操作。
20
+ 1. 确认已初始化 reldb,校验 `AuditInitConfigInput` 后初始化 audit;init 等待DDL就绪才成功,失败保持未初始化;每步失败停止后续操作。
21
21
  2. 在实际业务成功/失败边界记录事件,明确审计失败如何影响业务,不重复记录同一事件。
22
22
  3. 查询使用返回的分页元数据;清理前核对保留期及授权范围。凭证字段会被脱敏,个人信息仍需业务策略。
23
23
  4. 检查写入、查询和清理的 HaiResult,先关闭 audit 再关闭数据库。
24
24
 
25
+ 常见事件使用 `audit.helper` 与公开 `AuditHelper` 类型;内部记录器装配工厂不从包入口导出。
26
+
25
27
  ## 按需参考
26
28
 
27
29
  按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
@@ -2,6 +2,9 @@
2
2
 
3
3
  统一审计日志模块,基于 `@h-ai/reldb` 实现持久化,提供审计日志的记录、分页查询、清理与统计。
4
4
 
5
+
6
+ `audit.init()` 会等待自动建表和仓库就绪才返回成功;DDL 失败保持 `isInitialized=false`,修正数据库后可重试。自定义 JOIN、统计和清理也使用同一就绪屏障;初始化期间关闭会使迟到结果失效。先检查 init 的 HaiResult,再立即记录或查询;audit 不拥有共享 RelDB 的连接,关闭 audit 后再关闭数据库。
7
+
5
8
  ## 运行环境
6
9
 
7
10
  > ⚠️ **服务端模块(Node.js only)。** 审计日志由服务端自动记录,浏览器端通过 API 端点查询审计数据。
@@ -150,13 +150,15 @@ const db = await reldb.init(dbConfig.data)
150
150
  if (!db.success) return db
151
151
  const cached = await cache.init(cacheConfig.data)
152
152
  if (!cached.success) {
153
- await reldb.close()
154
- return cached
153
+ const cleanup = await reldb.close()
154
+ return cleanup.success ? cached : cleanup
155
155
  }
156
156
  const identity = await iam.init(iamConfig.data)
157
157
  if (!identity.success) {
158
- await cache.close()
159
- await reldb.close()
158
+ const cacheCleanup = await cache.close()
159
+ const dbCleanup = await reldb.close()
160
+ if (!cacheCleanup.success) return cacheCleanup
161
+ if (!dbCleanup.success) return dbCleanup
160
162
  return identity
161
163
  }
162
164
  // 应用结束时 await iam.close()、cache.close()、reldb.close();
@@ -11,17 +11,19 @@ description: "实现或排查 Memory/Redis 缓存、TTL、集合与分布式锁
11
11
  | --- | --- |
12
12
  | 能力 | 使用 @h-ai/cache 的 Memory/Redis 缓存、TTL、集合、排行榜和锁 |
13
13
  | 适用场景 | 缓存读写、过期策略或共享资源互斥 |
14
- | 输入 | CacheConfigInput、key/value、TTL、每次获锁的唯一 owner |
15
- | 输出 | 缓存和锁操作的 HaiResult,获锁 data 为 boolean |
14
+ | 输入 | CacheConfigInput、key/value、TTL;acquire 返回本次获锁唯一 owner |
15
+ | 输出 | 缓存和锁操作的 HaiResult,获锁 data 为唯一 owner 或 null |
16
16
  | 限制 | 仅服务端;Memory 不跨节点。release/extend 携带本次 owner,避免锁过期后误操作新持有者。 |
17
17
 
18
18
  ## 使用路径
19
19
 
20
20
  1. 先确定单进程 Memory 还是共享 Redis;配置校验与 init 成功后才使用。cache 不依赖 reldb。
21
21
  2. 根据 KV/Hash/List/Set/ZSet 选择公开操作,明确 TTL、空值和回源失败的业务含义。
22
- 3. 每次获锁生成唯一 owner,检查 success 与 data;续期/释放复用该 owner,丢锁后停止受保护工作。
22
+ 3. 读取 acquire 成功返回的唯一 owner,检查 success 与 data;续期/释放复用该 owner,丢锁后停止受保护工作。
23
23
  4. 验证过期、连接失败、并发与锁过期后的旧任务;最后 close。
24
24
 
25
+ Cluster 的 mset/集合运算/多键 blocking pop 须同 slot;mget/del/exists 可跨 slot,但无全局原子性。scan 使用字符串游标遍历全部主节点,返回去除 keyPrefix 的逻辑 key;拓扑/条件变化后重新遍历。
26
+
25
27
  ## 按需参考
26
28
 
27
29
  按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
@@ -33,3 +35,7 @@ description: "实现或排查 Memory/Redis 缓存、TTL、集合与分布式锁
33
35
  - [错误码 — `HaiCacheError`](reference.md#错误码--haicacheerror)
34
36
  - [常见模式](reference.md#常见模式)
35
37
  - [相关 Skills](reference.md#相关-skills)
38
+
39
+ 单键 compareAndSwap(key, expected, value, {px?,keepTtl?}) 比较 JSON 序列化快照并原子替换,必须检查 success 与 boolean data。expected=undefined 表示不存在,value=undefined 表示删除,null 是值;px 正整数且与 keepTtl 互斥。比较失败不修改 TTL;跨节点版本状态优先使用 CAS 而不是租约后无条件 SET。
40
+
41
+ `kv.set` 返回 `HaiResult<boolean>`:`data=true` 表示实际写入,NX/XX 未命中返回 `success=true,data=false` 且不改变原值或 TTL。签发票据、会话或登记公钥前必须检查这两个字段。`ex/px/exat/pxat` 分别使用秒、毫秒、Unix 秒、Unix 毫秒,须为正安全整数且只能指定一种,与 `keepTtl=true` 互斥;`nx/xx` 也互斥。零、负数、小数、非有限值或溢出返回 `INVALID_SET_OPTIONS`(`cache:016`),不会写成永久键。过去的正绝对时间表示立即过期。省略期限会移除原 TTL;`keepTtl` 保留已有 TTL,新键仍永久保存。memory 和 Redis 都保存 JSON 快照。
@@ -42,9 +42,23 @@ import { cache } from '@h-ai/cache'
42
42
 
43
43
  await cache.init(core.config.get('cache'))
44
44
  // 使用后关闭
45
- await cache.close()
45
+ const closed = await cache.close()
46
+ if (!closed.success) throw new Error(closed.error.message)
46
47
  ```
47
48
 
49
+
50
+ 初始化只在健康检查成功后发布连接;失败时释放候选资源。若清理仍失败,初始化错误的 `error.ext.cleanupPending` 为 `true`。`cache.close()` 返回 `Promise<HaiResult<void>>`,等待正在进行的初始化,并合并并发关闭。开始清理后 `isInitialized` 为 `false`,配置与业务操作不再可用;清理失败仍保留资源,后续 `close()` 或 `init()` 先重试释放,成功前不会创建替代连接。调用方必须检查关闭结果。
51
+
52
+ ```ts
53
+ const closed = await cache.close()
54
+ if (!closed.success) {
55
+ // 处理清理故障后有限重试;再次失败应向应用报告。
56
+ const retried = await cache.close()
57
+ if (!retried.success) throw new Error(retried.error.message)
58
+ }
59
+ ```
60
+
61
+
48
62
  `cache.config` 返回的是脱敏后的配置快照;Redis `password` / `url` 等敏感值不会原样暴露给日志或调试输出。
49
63
 
50
64
  ## 核心 API
@@ -69,6 +83,19 @@ if (result.success && result.data) {
69
83
  await cache.kv.del('user:123')
70
84
  ```
71
85
 
86
+ ### Cluster 多键与扫描
87
+
88
+ `mget/del/exists` 跨 slot 时有界并发且无整体原子性/快照;mget 保留输入顺序,DEL 失败可能已部分删除,重试幂等。`mset`、集合交/并/差和多键 blocking pop 须同 slot,否则返回 `CROSS_SLOT`;使用共同的 `{hash-tag}`,MSET 不做部分写入回退。
89
+
90
+ scan 首次传字符串 `'0'`,后续原样传回不透明游标,返回 `'0'` 表示完成。Cluster 覆盖所有 master;同配置和拓扑的另一应用节点可接续。拓扑、match 或 keyPrefix 改变时返回 `SCAN_TOPOLOGY_CHANGED`,从 `'0'` 重启;无效游标或 count 返回 `INVALID_SCAN`。返回去除 keyPrefix 的逻辑 key,前缀 glob 字符按字面量解释;keys 同样扫描并去重。SCAN 无快照,可能重复,count 仅为正整数提示值。
91
+
92
+ ```ts
93
+ const first = await cache.kv.scan('0', { match: 'users:*', count: 100 })
94
+ if (first.success && first.data[0] !== '0') {
95
+ const next = await cache.kv.scan(first.data[0], { match: 'users:*', count: 100 })
96
+ }
97
+ ```
98
+
72
99
  ### Hash/List/Set/ZSet(`cache.hash/list/set_/zset`)
73
100
 
74
101
  - `cache.hash`:对象字段读写(如用户 profile 局部更新)
@@ -87,16 +114,16 @@ await cache.zset.zadd('rank:daily', { member: 'u1', score: 100 })
87
114
 
88
115
  | 方法 | 说明 |
89
116
  | ---------- | ------------------------------------------ |
90
- | `acquire` | 尝试获锁(SET NX EX),返回 true/false |
91
- | `release` | 释放锁(支持 owner 验证,防止误释放) |
117
+ | `acquire` | 尝试获锁(SET NX EX),返回唯一 owner 或 null |
118
+ | `release` | 释放锁(必须携带本次 owner,防止误释放) |
92
119
  | `isLocked` | 检查锁是否被持有 |
93
- | `extend` | 续期锁 TTL(支持 owner 验证) |
120
+ | `extend` | 续期锁 TTL(必须携带本次 owner) |
94
121
 
95
122
  ```typescript
96
- // 每次尝试生成唯一 owner,后续释放/续期复用同一个值
97
- const owner = core.id.generate()
98
- const acquired = await cache.lock.acquire('my-lock', { ttl: 30, owner })
123
+ // acquire 返回本次唯一 owner,后续释放/续期必须复用该凭据
124
+ const acquired = await cache.lock.acquire('my-lock', { ttl: 30 })
99
125
  if (acquired.success && acquired.data) {
126
+ const owner = acquired.data
100
127
  try {
101
128
  // 长任务仅在仍持有锁时续期;失败或 data=false 时停止依赖锁的后续操作
102
129
  const extended = await cache.lock.extend('my-lock', 60, owner)
@@ -118,7 +145,7 @@ const locked = await cache.lock.isLocked('my-lock')
118
145
 
119
146
  **最佳实践:**
120
147
 
121
- - `owner` 每次获锁尝试唯一,释放/续期复用该值;仅用 nodeId 会使同节点旧任务误释放过期后重新获得的锁
148
+ - `owner` 必须取自成功的 acquire.data;缓存内部每次生成唯一凭据,不能使用 nodeId 替代。释放/续期复用该凭据。
122
149
  - 释放锁时传入 `owner` 防止误释放他人锁
123
150
  - Memory 后端适合开发/测试;Redis 后端用 Lua 脚本保证 release/extend 原子性
124
151
 
@@ -135,6 +162,9 @@ const locked = await cache.lock.isLocked('my-lock')
135
162
  | `HaiCacheError.NOT_INITIALIZED` | `hai:cache:010` | 未初始化 |
136
163
  | `HaiCacheError.UNSUPPORTED_TYPE` | `hai:cache:011` | 不支持的缓存类型 |
137
164
  | `HaiCacheError.CONFIG_ERROR` | `hai:cache:012` | 配置错误 |
165
+ | `HaiCacheError.INVALID_SCAN` | `hai:cache:013` | 游标或 count 无效 |
166
+ | `HaiCacheError.SCAN_TOPOLOGY_CHANGED` | `hai:cache:014` | 拓扑或条件变化,重新遍历 |
167
+ | `HaiCacheError.CROSS_SLOT` | `hai:cache:015` | 多键操作不在同一 slot |
138
168
 
139
169
  ## 常见模式
140
170
 
@@ -159,11 +189,11 @@ async function getUserCached(userId: string) {
159
189
 
160
190
  ```typescript
161
191
  const lockKey = 'batch:import'
162
- const owner = core.id.generate()
163
- const acquired = await cache.lock.acquire(lockKey, { ttl: 60, owner })
192
+ const acquired = await cache.lock.acquire(lockKey, { ttl: 60 })
164
193
  if (acquired.success && acquired.data) {
194
+ const owner = acquired.data
165
195
  try {
166
- await runBatchImport()
196
+ await runBatchImportWithConditionalCommit()
167
197
  }
168
198
  finally {
169
199
  await cache.lock.release(lockKey, owner)
@@ -185,3 +215,15 @@ else {
185
215
  - `hai-scheduler`:定时任务分布式锁(底层使用 cache.lock)
186
216
  - `hai-reach`:消息发送互斥锁(底层使用 cache.lock)
187
217
  - `hai-kit`:SvelteKit 集成层
218
+
219
+ Cluster 与 Sentinel 数据节点认证使用 `password`,Cluster 发现的新节点继承同一鉴权配置。`url` 模式只使用 URL 内凭据,单独 password 不覆盖 URL。
220
+
221
+ 单键 compareAndSwap(key, expected, value, {px?,keepTtl?}) 比较 JSON 序列化快照并原子替换,必须检查 success 与 boolean data。expected=undefined 表示不存在,value=undefined 表示删除,null 是值;px 正整数且与 keepTtl 互斥。比较失败不修改 TTL;跨节点版本状态优先使用 CAS 而不是租约后无条件 SET。
222
+
223
+ owner 只校验租约续期/释放,不是资源 fencing token。暂停或过期后旧任务仍可能执行,最终持久化写入必须使用资源自己的 CAS/代际条件/事务约束。批量导入等应用函数须在每次提交时校验条件,不能只靠锁包裹 Promise。
224
+
225
+ ## 生产共享状态
226
+
227
+ 生产仅使用 Redis(含 Cluster);memory 只用于开发/明确测试。Redis 故障返回失败,不回退本机缓存。 NODE_ENV 或 HAI_CORE_ENV 为 production 时在初始化成功前检查,只有 HAI_E2E=1/HAI_E2E_MOCK=1 明确测试豁免。不要将租约当作最终写入条件,外部未知效果须按稳定业务键核对。
228
+
229
+ `kv.set` 返回 `HaiResult<boolean>`:`data=true` 表示实际写入,NX/XX 未命中返回 `success=true,data=false` 且不改变原值或 TTL。签发票据、会话或登记公钥前必须检查这两个字段。`ex/px/exat/pxat` 分别使用秒、毫秒、Unix 秒、Unix 毫秒,须为正安全整数且只能指定一种,与 `keepTtl=true` 互斥;`nx/xx` 也互斥。零、负数、小数、非有限值或溢出返回 `INVALID_SET_OPTIONS`(`cache:016`),不会写成永久键。过去的正绝对时间表示立即过期。省略期限会移除原 TTL;`keepTtl` 保留已有 TTL,新键仍永久保存。memory 和 Redis 都保存 JSON 快照。