@h-ai/cli 0.1.0-alpha.66 → 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 (198) hide show
  1. package/README.en.md +86 -241
  2. package/README.ja.md +86 -241
  3. package/README.md +86 -241
  4. package/dist/{src-RDQ2EIBT.js → chunk-FI56GONY.js} +32 -2669
  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-QYX6V7H2.js → chunk-ZFNBUF77.js} +347 -276
  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-3XSFIXBP.js.map → cli-config-templates-W2LLPMB6.js.map} +1 -1
  12. package/dist/{cli-skill-templates-TSYEQTCY.js → cli-skill-templates-US5CCAJ4.js} +3 -3
  13. package/dist/{cli-skill-templates-TSYEQTCY.js.map → cli-skill-templates-US5CCAJ4.js.map} +1 -1
  14. package/dist/index.js +1304 -532
  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/README.md +48 -145
  21. package/templates/apps/admin/messages/ja-JP.json +38 -0
  22. package/templates/apps/admin/src/routes/+layout.svelte.hbs +16 -6
  23. package/templates/apps/admin/src/routes/admin/+layout.svelte.hbs +3 -3
  24. package/templates/apps/api/.dockerignore.hbs +14 -7
  25. package/templates/apps/api/.gitignore.hbs +7 -0
  26. package/templates/apps/api/apps/{{projectName}}-contract/README.md.hbs +23 -2
  27. package/templates/apps/api/apps/{{projectName}}-contract/src/index.ts.hbs +2 -0
  28. package/templates/apps/api/apps/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +4 -1
  29. package/templates/apps/api/apps/{{projectName}}-service/README.md.hbs +33 -8
  30. package/templates/apps/api/apps/{{projectName}}-service/config/_core.yml.hbs +0 -10
  31. package/templates/apps/api/apps/{{projectName}}-service/config/_serv.yml.hbs +2 -1
  32. package/templates/apps/api/apps/{{projectName}}-service/package.json.hbs +1 -2
  33. package/templates/apps/api/apps/{{projectName}}-service/src/app.ts.hbs +24 -11
  34. package/templates/apps/api/apps/{{projectName}}-service/src/index.ts.hbs +8 -1
  35. package/templates/apps/api/apps/{{projectName}}-service/src/lib/server/init.ts.hbs +68 -11
  36. package/templates/apps/api/apps/{{projectName}}-service/src/server/procedures/index.ts.hbs +10 -2
  37. package/templates/apps/api/e2e/api-service.spec.ts.hbs +2 -2
  38. package/templates/apps/api/hai.build.json.hbs +1 -1
  39. package/templates/apps/api/images/app.Dockerfile.hbs +35 -0
  40. package/templates/apps/api/package.json.hbs +6 -1
  41. package/templates/apps/api/playwright.config.ts.hbs +15 -7
  42. package/templates/apps/fullstack/.dockerignore.hbs +14 -9
  43. package/templates/apps/fullstack/.gitignore.hbs +7 -0
  44. package/templates/apps/fullstack/apps/{{projectName}}-app/README.md.hbs +24 -6
  45. package/templates/apps/fullstack/apps/{{projectName}}-app/index.html.hbs +1 -0
  46. package/templates/apps/fullstack/apps/{{projectName}}-app/messages/ja-JP.json.hbs +7 -0
  47. package/templates/apps/fullstack/apps/{{projectName}}-app/public/runtime-config.js.hbs +4 -0
  48. package/templates/apps/fullstack/apps/{{projectName}}-app/src/App.svelte.hbs +7 -1
  49. package/templates/apps/fullstack/apps/{{projectName}}-app/vite.config.ts.hbs +2 -2
  50. package/templates/apps/fullstack/apps/{{projectName}}-desktop/README.md.hbs +23 -5
  51. package/templates/apps/fullstack/apps/{{projectName}}-desktop/messages/ja-JP.json.hbs +7 -0
  52. package/templates/apps/fullstack/apps/{{projectName}}-desktop/vite.config.ts.hbs +1 -1
  53. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/README.md.hbs +16 -6
  54. package/templates/apps/fullstack/apps/{{projectName}}-miniapp/messages/ja-JP.json.hbs +12 -0
  55. package/templates/apps/fullstack/apps/{{projectName}}-web/README.md.hbs +24 -6
  56. package/templates/apps/fullstack/apps/{{projectName}}-web/messages/ja-JP.json.hbs +7 -0
  57. package/templates/apps/fullstack/apps/{{projectName}}-web/vite.config.ts.hbs +2 -2
  58. package/templates/apps/fullstack/e2e/fullstack.spec.ts.hbs +30 -9
  59. package/templates/apps/fullstack/hai.build.json.hbs +2 -2
  60. package/templates/apps/fullstack/images/app.Dockerfile.hbs +36 -0
  61. package/templates/apps/fullstack/images/web.Dockerfile.hbs +34 -0
  62. package/templates/apps/fullstack/package.json.hbs +3 -1
  63. package/templates/apps/fullstack/packages/{{projectName}}-contract/README.md.hbs +23 -2
  64. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/admin-schemas.ts.hbs +1 -1
  65. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/index.ts.hbs +2 -0
  66. package/templates/apps/fullstack/packages/{{projectName}}-contract/src/{{projectName}}-contract.ts.hbs +3 -0
  67. package/templates/apps/fullstack/packages/{{projectName}}-serv/README.md.hbs +32 -8
  68. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_core.yml.hbs +0 -6
  69. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_reach.yml.hbs +8 -0
  70. package/templates/apps/fullstack/packages/{{projectName}}-serv/config/_serv.yml.hbs +2 -1
  71. package/templates/apps/fullstack/packages/{{projectName}}-serv/package.json.hbs +6 -0
  72. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/app-server.ts.hbs +9 -5
  73. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/admin-service.ts.hbs +3 -2
  74. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/messages.ts.hbs +16 -1
  75. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/admin-procedures.ts.hbs +7 -3
  76. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/procedures/index.ts.hbs +3 -0
  77. package/templates/apps/fullstack/packages/{{projectName}}-serv/src/server/runtime.ts.hbs +58 -7
  78. package/templates/apps/fullstack/packages/{{projectName}}-serv/tests/app-server.test.ts.hbs +13 -3
  79. package/templates/apps/fullstack/packages/{{projectName}}-shared/.gitignore.hbs +7 -0
  80. package/templates/apps/fullstack/packages/{{projectName}}-shared/README.md.hbs +40 -14
  81. package/templates/apps/fullstack/packages/{{projectName}}-shared/messages/ja-JP.json.hbs +105 -0
  82. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AdminWorkspace.svelte.hbs +3 -3
  83. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/components/AppShell.svelte.hbs +6 -6
  84. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/audit.svelte.hbs +7 -7
  85. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/settings.svelte.hbs +4 -4
  86. package/templates/apps/fullstack/packages/{{projectName}}-shared/src/lib/pages/users.svelte.hbs +8 -8
  87. package/templates/apps/fullstack/packages/{{projectName}}-shared/tests/api-client.test.ts.hbs +13 -14
  88. package/templates/apps/fullstack/playwright.config.ts.hbs +15 -6
  89. package/templates/apps/h5/messages/ja-JP.json +40 -0
  90. package/templates/apps/h5/src/routes/+layout.svelte.hbs +16 -5
  91. package/templates/apps/mobile-app/messages/ja-JP.json +36 -0
  92. package/templates/apps/mobile-app/src/App.svelte.hbs +15 -5
  93. package/templates/apps/mobile-app/src/lib/api.ts.hbs +173 -14
  94. package/templates/apps/mobile-app/src/lib/pages/home.svelte.hbs +2 -2
  95. package/templates/apps/website/messages/ja-JP.json +32 -0
  96. package/templates/apps/website/src/routes/+layout.svelte.hbs +13 -5
  97. package/templates/apps/website/src/routes/services/+page.svelte.hbs +1 -1
  98. package/templates/base/.dockerignore.hbs +14 -8
  99. package/templates/base/.gitignore.hbs +7 -0
  100. package/templates/base/e2e/app.spec.ts.hbs +23 -8
  101. package/templates/base/hai.build.json.hbs +1 -1
  102. package/templates/base/{Dockerfile.hbs → images/app.Dockerfile.hbs} +19 -17
  103. package/templates/base/package.json.hbs +11 -3
  104. package/templates/base/playwright.config.ts +15 -4
  105. package/templates/base/src/hooks.server.ts.hbs +15 -6
  106. package/templates/base/src/lib/server/init.ts.hbs +139 -30
  107. package/templates/base/vite.config.ts.hbs +5 -3
  108. package/templates/features/iam/routes-shared/api/auth/logout/+server.ts +11 -6
  109. package/templates/features/payment/routes/api/v1/payment/create/+server.ts +18 -25
  110. package/templates/features/payment/routes/api/v1/payment/notify/[provider]/+server.ts +4 -0
  111. package/templates/i18n/project.inlang/settings.json +1 -1
  112. package/templates/shared/.gitattributes +6 -0
  113. package/templates/shared/e2e-env.ts +98 -0
  114. package/templates/shared/run-e2e.mjs +51 -12
  115. package/templates/skills/AGENTS.md +7 -3
  116. package/templates/skills/bridges/admin/AGENTS.md +6 -2
  117. package/templates/skills/bridges/api/AGENTS.md +3 -1
  118. package/templates/skills/bridges/fullstack/AGENTS.md.hbs +5 -1
  119. package/templates/skills/bridges/h5/AGENTS.md +6 -2
  120. package/templates/skills/bridges/mobile-app/AGENTS.md +6 -2
  121. package/templates/skills/bridges/website/AGENTS.md +6 -2
  122. package/templates/skills/hai-ai/SKILL.md +8 -2
  123. package/templates/skills/hai-ai/guide.md +134 -60
  124. package/templates/skills/hai-ai/reference.md +51 -11
  125. package/templates/skills/hai-api-client/SKILL.md +15 -160
  126. package/templates/skills/hai-api-client/reference.md +178 -0
  127. package/templates/skills/hai-api-contract/SKILL.md +16 -273
  128. package/templates/skills/hai-api-contract/reference.md +280 -0
  129. package/templates/skills/hai-app-create/SKILL.md +6 -2
  130. package/templates/skills/hai-app-create/development.md +24 -8
  131. package/templates/skills/hai-app-create/reference.md +7 -5
  132. package/templates/skills/hai-app-review/SKILL.md +7 -3
  133. package/templates/skills/hai-app-review/differential.md +34 -0
  134. package/templates/skills/hai-app-tests/SKILL.md +6 -3
  135. package/templates/skills/hai-app-tests/reference.md +5 -5
  136. package/templates/skills/hai-app-tests/validation.md +38 -0
  137. package/templates/skills/hai-audit/SKILL.md +24 -147
  138. package/templates/skills/hai-audit/reference.md +148 -0
  139. package/templates/skills/hai-build/SKILL.md +14 -8
  140. package/templates/skills/hai-build/reference.md +16 -13
  141. package/templates/skills/hai-cache/SKILL.md +20 -181
  142. package/templates/skills/hai-cache/reference.md +229 -0
  143. package/templates/skills/hai-capacitor/SKILL.md +7 -4
  144. package/templates/skills/hai-capacitor/reference.md +15 -9
  145. package/templates/skills/hai-ci/SKILL.md +2 -0
  146. package/templates/skills/hai-core/SKILL.md +7 -3
  147. package/templates/skills/hai-core/reference.md +25 -5
  148. package/templates/skills/hai-crypto/SKILL.md +22 -250
  149. package/templates/skills/hai-crypto/reference.md +310 -0
  150. package/templates/skills/hai-datapipe/SKILL.md +16 -195
  151. package/templates/skills/hai-datapipe/reference.md +204 -0
  152. package/templates/skills/hai-deploy/SKILL.md +24 -54
  153. package/templates/skills/hai-deploy/middleware.md +51 -43
  154. package/templates/skills/hai-framework-sync/SKILL.md +2 -2
  155. package/templates/skills/hai-framework-sync/sync.md +1 -1
  156. package/templates/skills/hai-iam/SKILL.md +25 -11
  157. package/templates/skills/hai-iam/reference.md +181 -154
  158. package/templates/skills/hai-kit/SKILL.md +35 -174
  159. package/templates/skills/hai-kit/reference.md +190 -0
  160. package/templates/skills/hai-payment/SKILL.md +33 -263
  161. package/templates/skills/hai-payment/reference.md +295 -0
  162. package/templates/skills/hai-pr-review/SKILL.md +21 -88
  163. package/templates/skills/hai-reach/SKILL.md +6 -3
  164. package/templates/skills/hai-reach/reference.md +83 -22
  165. package/templates/skills/hai-reldb/SKILL.md +6 -3
  166. package/templates/skills/hai-reldb/reference.md +68 -25
  167. package/templates/skills/hai-scheduler/SKILL.md +18 -195
  168. package/templates/skills/hai-scheduler/reference.md +216 -0
  169. package/templates/skills/hai-serv/SKILL.md +25 -10
  170. package/templates/skills/hai-serv/reference.md +123 -87
  171. package/templates/skills/hai-spec-implement/SKILL.md +5 -1
  172. package/templates/skills/hai-spec-implement/evaluation.md +18 -0
  173. package/templates/skills/hai-spec-implement/handoff.md +47 -0
  174. package/templates/skills/hai-spec-implement/templates.md +5 -0
  175. package/templates/skills/hai-spec-implement/workflow.md +5 -3
  176. package/templates/skills/hai-spec-review/SKILL.md +4 -0
  177. package/templates/skills/hai-storage/SKILL.md +23 -238
  178. package/templates/skills/hai-storage/reference.md +282 -0
  179. package/templates/skills/hai-ui/SKILL.md +17 -3
  180. package/templates/skills/hai-ui/guide.md +3 -3
  181. package/templates/skills/hai-ui/reference.md +26 -11
  182. package/templates/skills/hai-ui-design/SKILL.md +31 -0
  183. package/templates/skills/hai-ui-design/patterns.md +26 -0
  184. package/templates/skills/hai-ui-review/SKILL.md +30 -0
  185. package/templates/skills/hai-ui-review/checklist.md +22 -0
  186. package/templates/skills/hai-vecdb/SKILL.md +14 -10
  187. package/templates/skills/hai-vecdb/reference.md +33 -3
  188. package/dist/chunk-FWN5B54L.js +0 -468
  189. package/dist/chunk-FWN5B54L.js.map +0 -1
  190. package/dist/chunk-QYX6V7H2.js.map +0 -1
  191. package/dist/cli-config-templates-3XSFIXBP.js +0 -5
  192. package/dist/src-RDQ2EIBT.js.map +0 -1
  193. package/templates/apps/api/Dockerfile.hbs +0 -35
  194. package/templates/apps/fullstack/Dockerfile.hbs +0 -57
  195. /package/templates/apps/fullstack/{docker → images/web}/19-runtime-config.envsh.hbs +0 -0
  196. /package/templates/apps/fullstack/{docker → images/web}/nginx.conf.hbs +0 -0
  197. /package/templates/apps/fullstack/{docker → images/web}/runtime-config.js.template.hbs +0 -0
  198. /package/templates/base/{docker → images/web}/nginx.conf.hbs +0 -0
@@ -1,215 +1,38 @@
1
1
  ---
2
2
  name: hai-scheduler
3
- description: "使用 @h-ai/scheduler 注册 cron/API/JS/Hook 任务、手工触发并查询执行日志。"
3
+ description: "注册或排查 cron/API/JS/Hook 任务、手工触发、执行日志、重试取消与多节点调度。"
4
4
  ---
5
5
 
6
6
  # hai-scheduler
7
7
 
8
8
  ## 能力契约
9
9
 
10
- 持久化动态任务每个 `tickInterval`(默认 1000ms)在运行节点刷新;API 操作及获锁后的每次尝试重读定义,读取失败或任务已禁用/删除/改期时中断执行。`tasks` 仅表示最近成功刷新快照,已开始的外部副作用不会回滚。跨节点任务使用 `register()` 与共享数据库、Redis;`init({ tasks })` 是节点本地配置,memory cache 不支持跨进程防重。
11
-
12
- `refreshTasks()` 主动刷新而不执行,同一快照的并发读取合并;保存、更新和删除落库成功后才发布内存投影。清空可选策略使用 `retry: null` / `handler: null`。Hook 业务异常为 `failed` 并重试,缺处理器、取消或 `SchedulerTaskSkippedError` 为 `interrupted`;一次性任务只在成功后删除。`event.context.occurrenceId` 在一次发生的重试期间不变,宿主用其绑定持久幂等回执;跨周期长任务使用宿主持久租约。配置被替换时旧发生停止重试,成功清理前再次核对定义。
13
-
14
- 关闭必须 `await scheduler.close()` 后再关闭数据库和缓存:关闭会拒绝新任务、取消 HTTP/Worker/重试并等待执行链退出。生命周期回调通过事件 `signal` 协作取消,必须自行停止外部副作用,禁止在 Hook 阻塞主线程。旧任务取消后不重试、不删除新实例同 ID 的任务。
15
-
16
- JS 任务每次调用独立 Worker,默认 30000 毫秒期限包含线程启动;超时后等待线程终止才返回失败或重试,同步死循环不会占用主事件循环。上下文和结果须支持结构化克隆。Worker/vm 不构成安全沙箱,仍只接受受信任的服务端代码。
17
-
18
- 初始化为全有或全无:必须检查 `init()` 的 HaiResult。空 ID、重复 ID(包括与数据库记录重复)、无效 cron、持久化读取或解析失败均使初始化失败并清理状态;修正后重试,不会静默跳过任务。
19
-
20
- API 任务 `timeout` 默认 30000 毫秒,覆盖请求及完整响应体读取;`maxResponseBytes` 指定实际接收字节上限(正安全整数,默认 1048576 即 1 MiB),例如 `maxResponseBytes: 10 * 1024 * 1024` 允许最大 10 MiB;非法值在请求前失败。超时、超限和非成功 HTTP 状态均失败,重试由任务 retry 策略控制。
21
-
22
10
  | 项目 | 契约 |
23
11
  | --- | --- |
24
12
  | 能力 | 使用 @h-ai/scheduler 注册 cron/API/JS/Hook 任务、手工触发并查询执行日志 |
25
13
  | 适用场景 | 服务端任务调度、持久化和手工触发 |
26
14
  | 输入 | SchedulerConfigInput、任务定义、已初始化依赖与 hooks |
27
15
  | 输出 | 任务管理、触发和日志查询的 HaiResult |
28
- | 限制 | 仅服务端;vm 不是安全沙箱,JS 任务只执行可信代码。多节点需要共享锁,关闭释放调度资源。 |
29
-
30
- > `@h-ai/scheduler` 使用统一任务模型管理 API / JS / Hook 三类执行路径,支持持久化、手工触发来源记录、分布式锁与全局生命周期回调。
31
-
32
- ## 运行环境
33
-
34
- > ⚠️ **服务端模块(Node.js only)。** 浏览器端通过 API 端点触发 `scheduler.trigger()` 或查询任务日志。
35
-
36
- ## 依赖
37
-
38
- | 模块 | 用途 | 是否必需 | 初始化要求 |
39
- | --- | --- | --- | --- |
40
- | `@h-ai/reldb` | 任务定义与执行日志持久化 | `enableDb: true` 时必需 | 需在 `scheduler.init()` 前初始化 |
41
- | `@h-ai/cache` | 分布式锁 | 可选 | 初始化后自动启用 |
42
-
43
- ## 使用步骤
44
-
45
- ### 1. 初始化
46
-
47
- ```ts
48
- import { cache } from '@h-ai/cache'
49
- import { core } from '@h-ai/core'
50
- import { reldb } from '@h-ai/reldb'
51
- import { scheduler } from '@h-ai/scheduler'
52
-
53
- await reldb.init({ type: 'sqlite', database: './scheduler.db' })
54
- await cache.init({ type: 'memory' })
55
-
56
- await scheduler.init({
57
- enableDb: true,
58
- maxLogs: 1000,
59
- retentionDays: 30,
60
- hooks: {
61
- onTaskStart(event) {
62
- core.logger.info('Task started', { taskId: event.task.id, trigger: event.trigger })
63
- },
64
- onTaskInterrupted(event) {
65
- core.logger.warn('Task interrupted', { taskId: event.task.id, reason: event.reason })
66
- },
67
- onTaskFinish(event) {
68
- core.logger.info('Task finished', { status: event.log.status })
69
- },
70
- },
71
- })
72
- ```
73
-
74
- ### 2. 注册任务
75
-
76
- ```ts
77
- await scheduler.register({
78
- id: 'health-check',
79
- name: '健康检查',
80
- description: '每 5 分钟巡检一次接口健康状态',
81
- cron: '*/5 * * * *',
82
- params: { channel: 'ops' },
83
- retry: { maxAttempts: 3, backoffMs: [1000, 5000] },
84
- handler: {
85
- kind: 'api',
86
- url: 'https://api.example.com/health',
87
- method: 'GET',
88
- },
89
- })
90
-
91
- await scheduler.register({
92
- id: 'cleanup',
93
- name: '清理过期数据',
94
- description: '夜间单次清理任务',
95
- cron: '0 2 * * *',
96
- deleteAfterRun: true,
97
- params: { source: 'nightly' },
98
- handler: {
99
- kind: 'js',
100
- code: '(context) => ({ taskId: context.taskId, params: context.params })',
101
- },
102
- })
103
- ```
104
-
105
- > ⚠️ **安全警示**:`kind: 'js'` 仅允许受信任的服务端代码。当前实现使用 Node.js `vm` 便捷执行,不是安全沙箱;禁止把用户、租户或运营后台自由输入的 JS 字符串直接注册为任务。需要可配置执行逻辑时,优先改用 `kind: 'api'` 或 `hooks.onTaskExecute`。
106
-
107
- ### 3. 使用全局 execute hook 处理无 handler 任务
108
-
109
- ```ts
110
- await scheduler.setHooks({
111
- async onTaskExecute(event) {
112
- return { via: 'hook', source: event.context.trigger.source }
113
- },
114
- })
115
-
116
- await scheduler.register({
117
- id: 'hook-task',
118
- name: 'Hook 任务',
119
- cron: '* * * * *',
120
- })
121
- ```
122
-
123
- ### 4. 手工触发与日志查询
124
-
125
- ```ts
126
- const result = await scheduler.trigger('cleanup', { source: 'admin-console' })
127
-
128
- const logs = await scheduler.getLogs({
129
- taskId: 'cleanup',
130
- triggerType: 'manual',
131
- triggerSource: 'admin-console',
132
- startedAfter: Date.now() - 24 * 60 * 60 * 1000,
133
- startedBefore: Date.now(),
134
- pagination: { page: 1, pageSize: 20 },
135
- })
136
- ```
137
-
138
- ## 核心 API
139
-
140
- | 方法 / 属性 | 签名 | 说明 |
141
- | --- | --- | --- |
142
- | `init` | `(config?) => Promise<HaiResult<void>>` | 初始化调度器 |
143
- | `register` | `(task) => Promise<HaiResult<void>>` | 注册统一任务模型 |
144
- | `updateTask` | `(taskId, updates) => Promise<HaiResult<void>>` | 更新 cron / params / handler |
145
- | `refreshTasks` | `() => Promise<HaiResult<void>>` | 主动刷新共享定义,不执行任务 |
146
- | `register` 扩展字段 | `description / deleteAfterRun / retry` | 支持任务描述、一次性任务、失败重试策略 |
147
- | `trigger` | `(taskId, { source? }) => Promise<HaiResult<TaskExecutionLog>>` | 手工触发并记录来源 |
148
- | `getLogs` | `(options?) => Promise<HaiResult<PaginatedResult<TaskExecutionLog>>>` | 支持按 trigger + startedAfter/startedBefore 过滤日志 |
149
- | `setHooks` | `(hooks) => HaiResult<void>` | 设置全局生命周期回调 |
150
- | `clearHooks` | `() => HaiResult<void>` | 清空全局生命周期回调 |
151
- | `start / stop` | `() => HaiResult<void>` | 启动 / 停止调度 |
152
- | `close` | `() => Promise<void>` | 关闭调度器 |
153
-
154
- ## 常见模式
155
-
156
- ### 持久化 JS 任务
157
-
158
- ```ts
159
- await scheduler.init({ enableDb: true })
160
-
161
- await scheduler.register({
162
- id: 'persisted-js',
163
- name: '持久化 JS 任务',
164
- cron: '*/10 * * * *',
165
- handler: {
166
- kind: 'js',
167
- code: '(context) => ({ taskId: context.taskId })',
168
- },
169
- })
170
- ```
171
-
172
- ### 手工触发来源审计
173
-
174
- ```ts
175
- await scheduler.trigger('cleanup', { source: 'admin-console' })
176
- await scheduler.trigger('cleanup', { source: 'cli' })
16
+ | 限制 | 仅服务端;vm 不是安全沙箱,JS 任务只执行可信代码。共享RelDB强制持久journal/租约;unknown保留槽直至可信人工裁定。 |
177
17
 
178
- const adminLogs = await scheduler.getLogs({ triggerSource: 'admin-console' })
179
- ```
18
+ ## 使用路径
180
19
 
181
- ### 分布式锁
20
+ 1. 始终先初始化同一共享 reldb;enableDb 只能 true,跨主机生产使用 PostgreSQL/MySQL,不需要缓存。检查 scheduler.init 的 HaiResult。
21
+ 2. 动态共享任务通过 register 管理,不与 init.tasks 重复声明;读取失败、禁用、删除或改期时不得继续旧任务。
22
+ 3. 根据可信逻辑选择 API/JS/Hook。Worker/vm 不是安全沙箱;Hook 监听 signal,业务以 occurrenceId 原子持久去重,只有实际满足该约束才设 idempotent:true;API 自动覆盖幂等头。
23
+ 4. 明确 timeout、响应大小、重试与执行状态;关闭时 await scheduler.close,再关闭数据库;unknown须先确认旧runner终止并核对副作用,按版本人工resolveUnknown,禁止直接重排。
24
+ 5. 验证手动/定时、跨周期租约、手动/cron竞争及暂停接管、失败重试、取消与定义更新;共享租约必须在 SQL 内用存储时钟原子检查,cron 分钟身份也取 DB 时间且默认 UTC;补上节点 ±2h、不同 TZ 与真实行锁跨期限的多进程用例。
25
+ 6. 检查业务 status 与独立 persistence.status;pending 通过 execution.listLogFailures/repairLog 从已发布 journal 补写,不能重新触发业务。失租 unconfirmed 不能作为成功证据;验证丢回执和双节点并发补写。
182
26
 
183
- ```ts
184
- await cache.init({ type: 'redis', host: 'localhost', port: 6379 })
185
- await reldb.init({ type: 'sqlite', database: './scheduler.db' })
186
- await scheduler.init({
187
- enableDb: true,
188
- maxLogs: 500,
189
- retentionDays: 14,
190
- lockExpireMs: 300000,
191
- nodeId: 'node-1',
192
- })
193
- ```
27
+ 7. 核对 JSDoc 示例的真实入口与返回值:HaiResult 先分支,close 直接 await;标注“模块内部上下文”的仓库/runner 不属于根 API。示例编译与共享 DB/进程行为验证分别记录。
194
28
 
195
- ## 错误码 — `HaiSchedulerError`
29
+ ## 按需参考
196
30
 
197
- | 错误码 | code | 说明 |
198
- |--------|------|------|
199
- | `HaiSchedulerError.NOT_INITIALIZED` | `hai:scheduler:010` | 未初始化 |
200
- | `HaiSchedulerError.INIT_FAILED` | `hai:scheduler:011` | 初始化失败 |
201
- | `HaiSchedulerError.CONFIG_ERROR` | `hai:scheduler:012` | 配置错误 |
202
- | `HaiSchedulerError.TASK_NOT_FOUND` | `hai:scheduler:020` | 任务不存在 |
203
- | `HaiSchedulerError.TASK_ALREADY_EXISTS` | `hai:scheduler:021` | 任务已存在 |
204
- | `HaiSchedulerError.INVALID_CRON` | `hai:scheduler:022` | Cron 表达式无效 |
205
- | `HaiSchedulerError.EXECUTION_FAILED` | `hai:scheduler:023` | 执行失败 |
206
- | `HaiSchedulerError.JS_EXECUTION_FAILED` | `hai:scheduler:024` | JS 执行失败 |
207
- | `HaiSchedulerError.API_EXECUTION_FAILED` | `hai:scheduler:025` | API 执行失败 |
208
- | `HaiSchedulerError.DB_SAVE_FAILED` | `hai:scheduler:026` | DB 保存失败 |
209
- | `HaiSchedulerError.ALREADY_RUNNING` | `hai:scheduler:027` | 已在运行 |
210
- | `HaiSchedulerError.NOT_RUNNING` | `hai:scheduler:028` | 未在运行 |
211
- | `HaiSchedulerError.LOCK_ACQUIRE_FAILED` | `hai:scheduler:029` | 锁获取失败 |
212
- | `HaiSchedulerError.JS_COMPILE_FAILED` | `hai:scheduler:030` | JS 编译失败 |
213
- | `HaiSchedulerError.HOOK_EXECUTION_FAILED` | `hai:scheduler:031` | Hook 执行失败 |
31
+ 按本次操作读取对应小节;以已安装版本的公开类型核验签名,示例中的业务变量由应用提供。
214
32
 
215
- 调度循环按分钟时间点匹配 cron,并用同一分钟生成分布式锁键;启动或 tick 延迟至非零秒仍检查当前分钟,同一轮运行不会每个 tick 重复调度。
33
+ - [运行环境](reference.md#运行环境)
34
+ - [依赖](reference.md#依赖)
35
+ - [使用步骤](reference.md#使用步骤)
36
+ - [核心 API](reference.md#核心-api)
37
+ - [常见模式](reference.md#常见模式)
38
+ - [错误码 — `HaiSchedulerError`](reference.md#错误码--haischedulererror)
@@ -0,0 +1,216 @@
1
+ # hai-scheduler 参考
2
+
3
+ 所有节点必须先初始化同一共享 RelDB(跨主机生产使用 PostgreSQL/MySQL;SQLite 文件只适合可正确共享同一文件的本机进程)。数据库保存任务、执行 journal、全局任务占位和租约,`enableDb` 只能为 true;不再依赖缓存。`leaseMs` 默认 300000,范围 1000~86400000 毫秒,执行中自动续期。cron、手动和跨周期执行均争用同一任务槽,过期 owner 不能续活或提交。节点每个 `tickInterval` 刷新定义,`tasks` 只是最近成功快照;定义读取失败、删除、禁用、改期均中断旧尝试,已发生副作用不会回滚。
4
+
5
+ `context.occurrenceId` 是持久发生标识;同一 cron 分钟跨节点一致,手动可传 1~128 字符 `idempotencyKey` 重用原记录,省略时每次独立。`executionToken` 每次领取变化,`sequence` / `attempt` 提供执行代信息。API 会覆盖 `Idempotency-Key`、`X-Scheduler-Sequence`、`X-Scheduler-Attempt`、`X-Scheduler-Task`(任务 ID 使用 percent-encoding)。Hook 及生命周期回调已领取时的 `event.context` 使用同一发生标识;观测回调若产生副作用也须去重。
6
+
7
+ `idempotent` 默认为 false;只有接收端已将幂等账本和全部副作用原子持久提交,才声明 true。租约过期的已执行任务仅在 true 时以同 occurrenceId 接管;非幂等或耗尽本 occurrence 保存的 retry.maxAttempts(默认 1,范围 1–100,包含首次执行及恢复)时进入 `unknown` 并继续占住任务槽。租约无法强制撤销暂停进程已发出的外部操作,单有 HTTP 头不构成去重实现。重试同样受幂等声明约束。
8
+
9
+ `scheduler.execution.get(occurrenceId)` 查询权威状态;`listUnknown({ limit, offset })` 分页获取未知结果,limit 为 1~100,offset 为 0~1000000。可信人工先核实外部效果、确认旧 runner 已终止,再按 `expectedUpdatedAt` CAS 调用 `resolveUnknown(id, { status: 'success' | 'discarded', expectedUpdatedAt })` 释放占位;不能直接把 unknown 重排。`log.occurrenceId` 可作查询回执;`getLogs` 仅为尝试观测日志,日志保留策略不删除 journal/幂等历史。
10
+
11
+ ## 运行环境
12
+
13
+ > ⚠️ **服务端模块(Node.js only)。** 浏览器端通过 API 端点触发 `scheduler.trigger()` 或查询任务日志。
14
+
15
+ ## 依赖
16
+
17
+ | 模块 | 用途 | 是否必需 | 初始化要求 |
18
+ | --- | --- | --- | --- |
19
+ | `@h-ai/reldb` | 任务/执行 journal/租约 | 始终必需 | 先初始化同一共享数据库 |
20
+
21
+ ## 使用步骤
22
+
23
+ ### 1. 初始化
24
+
25
+ ```ts
26
+ import { core } from '@h-ai/core'
27
+ import { reldb } from '@h-ai/reldb'
28
+ import { scheduler } from '@h-ai/scheduler'
29
+
30
+ await reldb.init({ type: 'sqlite', database: './scheduler.db' })
31
+
32
+ await scheduler.init({
33
+ enableDb: true,
34
+ maxLogs: 1000,
35
+ retentionDays: 30,
36
+ hooks: {
37
+ onTaskStart(event) {
38
+ core.logger.info('Task started', { taskId: event.task.id, trigger: event.trigger })
39
+ },
40
+ onTaskInterrupted(event) {
41
+ core.logger.warn('Task interrupted', { taskId: event.task.id, reason: event.reason })
42
+ },
43
+ onTaskFinish(event) {
44
+ core.logger.info('Task finished', { status: event.log.status })
45
+ },
46
+ },
47
+ })
48
+ ```
49
+
50
+ ### 2. 注册任务
51
+
52
+ ```ts
53
+ await scheduler.register({
54
+ id: 'health-check',
55
+ name: '健康检查',
56
+ description: '每 5 分钟巡检一次接口健康状态',
57
+ cron: '*/5 * * * *',
58
+ params: { channel: 'ops' },
59
+ handler: {
60
+ kind: 'api',
61
+ url: 'https://api.example.com/health',
62
+ method: 'GET',
63
+ },
64
+ })
65
+
66
+ await scheduler.register({
67
+ id: 'cleanup',
68
+ name: '清理过期数据',
69
+ description: '夜间单次清理任务',
70
+ cron: '0 2 * * *',
71
+ deleteAfterRun: true,
72
+ params: { source: 'nightly' },
73
+ handler: {
74
+ kind: 'js',
75
+ code: '(context) => ({ taskId: context.taskId, params: context.params })',
76
+ },
77
+ })
78
+ ```
79
+
80
+ > ⚠️ **安全警示**:`kind: 'js'` 仅允许受信任的服务端代码。当前实现在线程内使用 Node.js `vm`,超时可终止 Worker,但不构成安全沙箱;禁止把用户、租户或运营后台自由输入的 JS 字符串直接注册为任务。需要可配置执行逻辑时,优先改用 `kind: 'api'` 或 `hooks.onTaskExecute`。
81
+
82
+ ### 3. 使用全局 execute hook 处理无 handler 任务
83
+
84
+ ```ts
85
+ await scheduler.setHooks({
86
+ async onTaskExecute(event) {
87
+ return { via: 'hook', source: event.context.trigger.source }
88
+ },
89
+ })
90
+
91
+ await scheduler.register({
92
+ id: 'hook-task',
93
+ name: 'Hook 任务',
94
+ cron: '* * * * *',
95
+ })
96
+ ```
97
+
98
+ ### 4. 手工触发与日志查询
99
+
100
+ ```ts
101
+ const result = await scheduler.trigger('cleanup', { source: 'admin-console' })
102
+
103
+ const logs = await scheduler.getLogs({
104
+ taskId: 'cleanup',
105
+ triggerType: 'manual',
106
+ triggerSource: 'admin-console',
107
+ startedAfter: Date.now() - 24 * 60 * 60 * 1000,
108
+ startedBefore: Date.now(),
109
+ pagination: { page: 1, pageSize: 20 },
110
+ })
111
+ ```
112
+
113
+ ## 核心 API
114
+
115
+ | 方法 / 属性 | 签名 | 说明 |
116
+ | --- | --- | --- |
117
+ | `init` | `(config?) => Promise<HaiResult<void>>` | 初始化调度器 |
118
+ | `register` | `(task) => Promise<HaiResult<void>>` | 注册统一任务模型 |
119
+ | `updateTask` | `(taskId, updates) => Promise<HaiResult<void>>` | 更新 cron / params / handler |
120
+ | `refreshTasks` | `() => Promise<HaiResult<void>>` | 主动刷新共享定义,不执行任务 |
121
+ | `register` 扩展字段 | `description / deleteAfterRun / retry / idempotent` | 支持任务描述、一次性任务、失败重试策略 |
122
+ | `trigger` | `(taskId, { source?, idempotencyKey? }) => Promise<HaiResult<TaskExecutionLog>>` | 手工触发并记录来源 |
123
+ | `execution.listLogFailures / repairLog` | `(options?) / (occurrenceId)` | 有界监控并幂等补写已发布 journal,不重执行业务 |
124
+ | `getLogs` | `(options?) => Promise<HaiResult<PaginatedResult<TaskExecutionLog>>>` | 支持按 trigger + startedAfter/startedBefore 过滤日志 |
125
+ | `setHooks` | `(hooks) => HaiResult<void>` | 设置全局生命周期回调 |
126
+ | `clearHooks` | `() => HaiResult<void>` | 清空全局生命周期回调 |
127
+ | `start / stop` | `() => HaiResult<void>` | 启动 / 停止调度 |
128
+ | `close` | `() => Promise<void>` | 关闭调度器 |
129
+
130
+ ## 常见模式
131
+
132
+ ### 持久化 JS 任务
133
+
134
+ ```ts
135
+ await scheduler.init({ enableDb: true })
136
+
137
+ await scheduler.register({
138
+ id: 'persisted-js',
139
+ name: '持久化 JS 任务',
140
+ cron: '*/10 * * * *',
141
+ handler: {
142
+ kind: 'js',
143
+ code: '(context) => ({ taskId: context.taskId })',
144
+ },
145
+ })
146
+ ```
147
+
148
+ ### 手工触发来源审计
149
+
150
+ ```ts
151
+ await scheduler.trigger('cleanup', { source: 'admin-console' })
152
+ await scheduler.trigger('cleanup', { source: 'cli' })
153
+
154
+ const adminLogs = await scheduler.getLogs({ triggerSource: 'admin-console' })
155
+ ```
156
+
157
+ ### 共享执行租约
158
+
159
+ ```ts
160
+ await reldb.init({ type: 'sqlite', database: './scheduler.db' })
161
+ await scheduler.init({
162
+ enableDb: true,
163
+ maxLogs: 500,
164
+ retentionDays: 14,
165
+ leaseMs: 300000,
166
+ nodeId: 'node-1',
167
+ })
168
+ ```
169
+
170
+ ### 日志失败与补写
171
+
172
+ 日志的 `id` 是每次尝试固定的 UUID,`persistence.status` 与业务 `status` 独立:`persisted` 表示日志及确认回执已持久化;`pending` 表示业务终态已提交但日志待补写;`unconfirmed` 表示没有已确认的业务终态,不能据此补写成功。只有 owner 与有效租约条件提交的终态会发布完整尝试列表,随后才投影日志;失租节点不能投影成功。日志失败不会把业务成功改成失败,也不会因此重跑业务。
173
+
174
+ 补写会缩小 pending 列表;批量补写应反复读取 offset=0,避免一边补写一边增加 offset 跳过记录。
175
+
176
+ 可信服务端用 `execution.listLogFailures({ limit, offset })` 监控 pending(范围与 listUnknown 相同),用 `execution.repairLog(occurrenceId)` 从共享 journal 幂等补写;可跨节点、重启及并发调用。写入已提交但回执丢失时通过固定主键和完整内容核对去重。补写返回 `LOG_PERSIST_FAILED`(032)时继续保留 pending,并输出安全告警;全部数据库不可用且终态未提交时仍按 unknown 核对外部效果,不能假定存在可恢复结果。补写与日志保留均不调用业务 Hook。保留策略失败仅告警,不撤销已保存日志。`onTaskFinish` 是本地业务完成通知,其 unconfirmed 状态不是持久化确认;以 trigger 返回值及 execution.get 的 logPersistence 为准。
177
+
178
+ 领取前竞争/跳过的 interrupted 仅为观测日志,没有可补写的终态;其写入未确认时返回 unconfirmed。JSON 大载荷保存完整结果和全部重试记录,避免 MySQL 的 255 字符截断;新 schema 不自动迁移旧日志/执行表。关闭会等待在途补写,先关闭 scheduler 再关闭数据库。
179
+
180
+ ```ts
181
+ const result = await scheduler.trigger('cleanup', { idempotencyKey: 'cleanup-2026-10-23' })
182
+ if (!result.success)
183
+ return result
184
+ if (result.data.persistence.status === 'pending' && result.data.occurrenceId) {
185
+ const repaired = await scheduler.execution.repairLog(result.data.occurrenceId)
186
+ if (!repaired.success)
187
+ return repaired
188
+ }
189
+ // 不用日志补写失败重新 trigger 业务;定期按页扫描 listLogFailures。
190
+ ```
191
+
192
+ ## 错误码 — `HaiSchedulerError`
193
+
194
+ | 错误码 | code | 说明 |
195
+ |--------|------|------|
196
+ | `HaiSchedulerError.NOT_INITIALIZED` | `hai:scheduler:010` | 未初始化 |
197
+ | `HaiSchedulerError.INIT_FAILED` | `hai:scheduler:011` | 初始化失败 |
198
+ | `HaiSchedulerError.CONFIG_ERROR` | `hai:scheduler:012` | 配置错误 |
199
+ | `HaiSchedulerError.TASK_NOT_FOUND` | `hai:scheduler:020` | 任务不存在 |
200
+ | `HaiSchedulerError.TASK_ALREADY_EXISTS` | `hai:scheduler:021` | 任务已存在 |
201
+ | `HaiSchedulerError.INVALID_CRON` | `hai:scheduler:022` | Cron 表达式无效 |
202
+ | `HaiSchedulerError.EXECUTION_FAILED` | `hai:scheduler:023` | 执行失败 |
203
+ | `HaiSchedulerError.JS_EXECUTION_FAILED` | `hai:scheduler:024` | JS 执行失败 |
204
+ | `HaiSchedulerError.API_EXECUTION_FAILED` | `hai:scheduler:025` | API 执行失败 |
205
+ | `HaiSchedulerError.DB_SAVE_FAILED` | `hai:scheduler:026` | DB 保存失败 |
206
+ | `HaiSchedulerError.ALREADY_RUNNING` | `hai:scheduler:027` | 已在运行 |
207
+ | `HaiSchedulerError.NOT_RUNNING` | `hai:scheduler:028` | 未在运行 |
208
+ | `HaiSchedulerError.JS_COMPILE_FAILED` | `hai:scheduler:030` | JS 编译失败 |
209
+ | `HaiSchedulerError.HOOK_EXECUTION_FAILED` | `hai:scheduler:031` | Hook 执行失败 |
210
+ | `HaiSchedulerError.LOG_PERSIST_FAILED` | `hai:scheduler:032` | 日志持久化未确认 |
211
+
212
+ 调度循环按分钟匹配 cron,同一分生成稳定的持久 occurrenceId。
213
+
214
+ 数据库是共享租约的时间权威:claim/begin/renew/finish/expired/isolate 均在执行 SQL 内取存储端时钟并原子核验期限,不用节点 Date.now 或“先查 now 再写”的结果授权。PostgreSQL clock_timestamp、MySQL SYSDATE(3)、SQLite now 在真实锁等待后仍拒绝过期提交;updatedAt 条件版本使用同一数据库时钟并单调推进。cron 从 DB 时间生成分钟身份并匹配;默认 timezone 为 UTC,显式 IANA 时区只改变墙上时间解释,不读取节点 TZ。节点偏差 ±2h、不同 TZ 下同一分钟身份、过期但未接管、接管后的旧 owner 和行锁跨期限均须真实多进程验证。应用仍维护 DB 时钟与外部接收端的持久幂等/fence;未知效果不能靠补写日志或重新 trigger 重做。
215
+
216
+ 公开使用以 @h-ai/scheduler 的实际入口与声明为准。声明 JSDoc 的公开示例检查 HaiResult,关闭调用使用 await scheduler.close()(返回 Promise<void>);内部 repository/runner/compiler/cron 示例标注“模块内部上下文”,不能照抄为根导入。业务 status 与 persistence 分开,pending 补写不会重跑业务;unknown/unconfirmed 先核对共享 journal 与外部效果。应用使用所安装版本的公开类型,并通过本应用已有的类型检查验证使用示例。
@@ -1,31 +1,40 @@
1
1
  ---
2
2
  name: hai-serv
3
- description: "使用 @h-ai/serv 装配 contract/procedures、认证守卫、OpenAPI、HTTP 与语音 WebSocket。"
3
+ description: "为独立 API 服务装配 contract/procedures、认证守卫、Node/Fetch、OpenAPI 与语音 WebSocket。"
4
4
  ---
5
5
 
6
6
  # hai-serv
7
7
 
8
+ transport 仅支持 v2:SM2 发送方签名、独立密钥的 Encrypt-then-HMAC、HTTP 上下文/响应状态绑定及共享原子防重放。GET/HEAD、空 POST 与空响应也认证;无降级开关。TLS 信任边界、5 分钟窗口、nonce 持久化与实现示例见 [参考](reference.md)。
9
+
8
10
  ## 能力契约
9
11
 
10
- | 项目 | 契约 |
11
- | --- | --- |
12
- | 能力 | 使用 @h-ai/serv 装配 contract/procedures、认证守卫、OpenAPI、HTTP 与语音 WebSocket |
13
- | 适用场景 | 独立 HTTP 服务装配、请求守卫或运行时适配 |
14
- | 输入 | contract、procedures、已初始化 features、ServConfigInput |
15
- | 输出 | HTTP App、Node listener 或 Fetch handler |
16
- | 限制 | 无 init/close;createApp 配置错误可抛出。依赖先初始化,onClose 反序释放。Hono 为内部实现,业务授权由 route guard 执行。 |
12
+ | 项目 | 契约 |
13
+ | -------- | ----------------------------------------------------------------------------------------------------------------------- |
14
+ | 能力 | 使用 @h-ai/serv 装配 contract/procedures、认证守卫、OpenAPI、HTTP 与语音 WebSocket |
15
+ | 适用场景 | 独立 HTTP 服务装配、请求守卫或运行时适配 |
16
+ | 输入 | contract、procedures、已初始化 features、ServConfigInput |
17
+ | 输出 | HTTP App、Node listener 或 Fetch handler |
18
+ | 限制 | 无 init/close;createApp 配置错误可抛出。依赖先初始化,onClose 反序释放。Hono 为内部实现,业务授权由 route guard 执行。 |
17
19
 
18
20
  ## 使用路径
19
21
 
20
- - 先确定 Node listen 还是 Fetch adapter,再从共享 contract 装配 procedures;不要直接暴露 Hono。Node 自动提供 socket 对端;Fetch 通过 app.fetch 的 remoteAddress 元数据提供可信平台对端。http.trustedProxies 默认为空,仅显式代理 IP/CIDR 可以提供转发头;自定义 context 传递完整 input 保留解析后的 ip。
22
+ - 在满足包 Node API 依赖的宿主中选择 listen 或标准 Fetch adapter,再从共享 contract 装配 procedures;不要直接暴露 Hono。Node 自动提供 socket 对端;Fetch 通过 app.fetch 的 remoteAddress 元数据提供可信平台对端。http.trustedProxies 默认为空,仅显式代理 IP/CIDR 可以提供转发头;自定义 context 传递完整 input 保留解析后的 ip。
23
+ - Storage 默认 procedures 必须注入 authorize 资源策略,使用可信 session 校验全部 key/prefix;缺失策略默认拒绝,混合批量全量授权后才执行。
24
+ - AI 默认 feature 以可信 session.userId 绑定 objectId,所有读写和记忆按主体隔离;跨主体访问必须显式 authorizeObject,同名 sessionId 不代表同一用户。
21
25
  - 认证绑定 route 的 auth/permission/role;启动配置错误与正常请求领域错误分别处理。
22
26
  - 普通 procedure 的失败 HaiResult 默认通过 HTTP 200 返回,必须检查 success 与 error.code;error.httpStatus 是领域错误元数据。contract 输入校验失败为 HTTP 400,HTTP middleware、刷新 cookie 等响应另按自身契约处理,不能混为同一种状态映射。
23
27
  - 浏览器 Cookie、CORS、apiPrefix、transport 须与 api-client 同步;语音 ticket 单独核验。
28
+ - transport 服务端显式注入受管 serverKeyRing 和共享 keyStore;请求带协商 serverKeyId。私钥只从 Secret/私有环境加载,不放公开配置;轮换、重启与跨节点要求见 hai-crypto。
24
29
  - 缺失路由返回本地化 HaiResult JSON 404;加密传输保留该状态。默认 feature 可按需选用,contract 与挂载的 procedures 保持一致。
25
30
 
31
+ 传输加密按实际请求/响应 body 判断空正文,不信任 Content-Type/Length;非空明文不能借缺头、chunked 或伪造零长度旁路,实际空流保留无 input POST 行为。
32
+
33
+ transport 与 kit/客户端共享固定 UTF-8 字节预算:握手 JSON 16 KiB、公钥 4 KiB、明文 1 MiB、密文 JSON 2 MiB + 32 KiB。原流有界读取,超限立即取消并释放 reader;请求超限 413、响应超限 500。重建 Request 保留 signal 并清空 Hono bodyCache,移除旧长度/编码头;不新增 YAML/环境变量开关。
34
+
26
35
  ## 按需参考
27
36
 
28
- 详细配置、API 签名、错误码和范本在 [reference.md](reference.md)。按下列标题定位所需小节,不默认通读;用例中的业务变量需结合当前应用补齐。
37
+ 按任务读取 [reference.md](reference.md) 的对应小节;签名以当前源码或安装版本的公开类型为准,不默认加载全部资料。示例中的业务变量由应用提供。
29
38
 
30
39
  - 运行环境
31
40
  - 适用场景
@@ -37,3 +46,9 @@ description: "使用 @h-ai/serv 装配 contract/procedures、认证守卫、Open
37
46
  - 错误码
38
47
  - httpOnly Cookie 认证
39
48
  - 测试
49
+
50
+ 标准登出入口 `auth.logout` 从 input.accessToken 或 Authorization Bearer 取得要撤销的完整令牌,不要求 access 仍有效。它不接受 userId 授权或撤销目标;缺少令牌返回失败,未知令牌幂等成功。共享存储故障原样保留失败 HaiResult,Cookie middleware 只对成功结果清理 refresh Cookie。普通 procedure 继续使用既有 HTTP 200 / HaiResult 协议,客户端必须检查 success,不能只检查状态码。
51
+
52
+ JSDoc 公开例子按实际根入口/features 声明编译;“模块内部上下文”不能照抄为根 API。工厂配置异常、领域 HaiResult 与 HTTP middleware 的状态分别处理;listener.close 仅关闭 HTTP,onClose 为信号关闭回调,业务资源仍由宿主管理。
53
+
54
+ 使用 toFetch 注入时,Request 输入同样应用 RequestInit 覆盖,并按标准构造器拒绝非法/已消费/锁定正文;取消会停止派发或响应等待并传给应用,不回滚副作用。Response 交付后的流由应用监听 request.signal 清理。