create-windy 0.3.18 → 0.4.0

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 (216) hide show
  1. package/README.md +28 -13
  2. package/dist/cli.js +788 -280
  3. package/package.json +1 -1
  4. package/template/.agents/skills/windy-business-module/SKILL.md +34 -10
  5. package/template/.windy-template.json +2 -2
  6. package/template/AGENTS.md +18 -8
  7. package/template/README.md +32 -22
  8. package/template/apps/agent-server/src/southwind_agent_server/adk_debug.py +38 -5
  9. package/template/apps/agent-server/tests/test_adk_debug_result_polling.py +139 -0
  10. package/template/apps/server/Dockerfile +1 -0
  11. package/template/apps/server/package.json +1 -0
  12. package/template/apps/server/scripts/validate-module-composition-once.ts +4 -0
  13. package/template/apps/server/scripts/validate-module-composition.ts +28 -0
  14. package/template/apps/server/src/agent/data-scope-routes.test.ts +20 -5
  15. package/template/apps/server/src/agent/routes.test.ts +20 -7
  16. package/template/apps/server/src/agent/routes.ts +14 -0
  17. package/template/apps/server/src/agent/runtime-bootstrap.ts +22 -6
  18. package/template/apps/server/src/agent/sandbox-worker.ts +2 -2
  19. package/template/apps/server/src/agent/unavailable-routes.ts +33 -0
  20. package/template/apps/server/src/app/server-composition.ts +56 -0
  21. package/template/apps/server/src/application-services.ts +2 -0
  22. package/template/apps/server/src/bulk-data/service.ts +14 -0
  23. package/template/apps/server/src/data-governance/export/authorization-port.ts +1 -1
  24. package/template/apps/server/src/data-governance/export/postgres-api-fixture.ts +18 -2
  25. package/template/apps/server/src/data-governance/export/postgres-recovery.integration.test.ts +10 -9
  26. package/template/apps/server/src/data-governance/export/routes-test-fixture.ts +6 -8
  27. package/template/apps/server/src/data-governance/user-plaintext-routes.test.ts +4 -2
  28. package/template/apps/server/src/feature/routes.authorization.test.ts +26 -1
  29. package/template/apps/server/src/feature/routes.ts +33 -15
  30. package/template/apps/server/src/foundation-activation.test.ts +60 -0
  31. package/template/apps/server/src/foundation.ts +64 -5
  32. package/template/apps/server/src/guards.test.ts +23 -0
  33. package/template/apps/server/src/guards.ts +12 -0
  34. package/template/apps/server/src/index.ts +28 -34
  35. package/template/apps/server/src/installed-business-modules.ts +13 -24
  36. package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +1 -0
  37. package/template/apps/server/src/jobs/legacy-adapter.test.ts +106 -0
  38. package/template/apps/server/src/jobs/legacy-service-runtime.ts +40 -0
  39. package/template/apps/server/src/license/activation.test.ts +30 -0
  40. package/template/apps/server/src/license/activation.ts +26 -0
  41. package/template/apps/server/src/license/restriction.test.ts +16 -0
  42. package/template/apps/server/src/license/routes-offline-code.test.ts +6 -2
  43. package/template/apps/server/src/license/routes.ts +20 -4
  44. package/template/apps/server/src/license/runtime-service.test.ts +10 -2
  45. package/template/apps/server/src/module-bootstrap.ts +14 -23
  46. package/template/apps/server/src/module-composition.test.ts +43 -2
  47. package/template/apps/server/src/module-composition.ts +29 -0
  48. package/template/apps/server/src/module-host/durable-context-guard.test.ts +172 -0
  49. package/template/apps/server/src/module-host/durable-context-unavailable.test.ts +50 -1
  50. package/template/apps/server/src/module-host/durable-context.test.ts +42 -25
  51. package/template/apps/server/src/module-host/durable-context.ts +42 -2
  52. package/template/apps/server/src/module-host/durable-handler-registration.ts +96 -5
  53. package/template/apps/server/src/module-host/implementation-completeness.test.ts +185 -0
  54. package/template/apps/server/src/module-host/implementation-completeness.ts +132 -0
  55. package/template/apps/server/src/module-host/initialize-contracts.ts +14 -0
  56. package/template/apps/server/src/module-host/initialize.test.ts +78 -2
  57. package/template/apps/server/src/module-host/initialize.ts +15 -4
  58. package/template/apps/server/src/module-host/project-attachments.test.ts +92 -0
  59. package/template/apps/server/src/module-host/project-attachments.ts +58 -0
  60. package/template/apps/server/src/module-host/project-composition-package.test.ts +186 -0
  61. package/template/apps/server/src/module-host/project-composition.test.ts +260 -0
  62. package/template/apps/server/src/module-host/project-composition.ts +311 -0
  63. package/template/apps/server/src/module-host/request-context.ts +3 -0
  64. package/template/apps/server/src/module-host/route-declaration.ts +15 -6
  65. package/template/apps/server/src/module-host/route-installer.test.ts +15 -2
  66. package/template/apps/server/src/module-host/route-installer.ts +12 -2
  67. package/template/apps/server/src/module-host/route-registration.test.ts +13 -4
  68. package/template/apps/server/src/module-host/runtime-composition-compatibility.test.ts +63 -0
  69. package/template/apps/server/src/module-host/runtime-composition-compatibility.ts +46 -0
  70. package/template/apps/server/src/module-host/runtime-composition.ts +34 -0
  71. package/template/apps/server/src/module-host/search-provider-registration.test.ts +64 -0
  72. package/template/apps/server/src/module-host/search-provider-registration.ts +35 -0
  73. package/template/apps/server/src/module-host/static-route-authorization.test.ts +99 -0
  74. package/template/apps/server/src/module-host/static-route-authorization.ts +90 -0
  75. package/template/apps/server/src/module-host/task-definitions.ts +1 -0
  76. package/template/apps/server/src/module-host/user-directory-composition.test.ts +4 -1
  77. package/template/apps/server/src/module-host.test.ts +2 -0
  78. package/template/apps/server/src/module-preflight.test.ts +289 -0
  79. package/template/apps/server/src/module-preflight.ts +96 -0
  80. package/template/apps/server/src/module-runtime-validation.ts +1 -0
  81. package/template/apps/server/src/platform-app-bootstrap.ts +50 -0
  82. package/template/apps/server/src/platform-http-runtime.ts +32 -7
  83. package/template/apps/server/src/route-guards.test.ts +22 -0
  84. package/template/apps/server/src/route-guards.ts +10 -3
  85. package/template/apps/server/src/runtime-audit-query.ts +50 -0
  86. package/template/apps/server/src/runtime-feature.test.ts +46 -0
  87. package/template/apps/server/src/runtime-feature.ts +5 -0
  88. package/template/apps/server/src/runtime.ts +10 -49
  89. package/template/apps/server/src/scheduler/audit-action.test.ts +49 -0
  90. package/template/apps/server/src/scheduler/audit.ts +1 -1
  91. package/template/apps/server/src/scheduler/execution-recorder.ts +10 -5
  92. package/template/apps/server/src/scheduler/recovery-service.test.ts +4 -3
  93. package/template/apps/server/src/scheduler/service-idle.test.ts +4 -3
  94. package/template/apps/server/src/scheduler/service.test.ts +1 -0
  95. package/template/apps/server/src/scheduler/service.ts +18 -21
  96. package/template/apps/server/src/scheduler/types.ts +5 -1
  97. package/template/apps/server/src/startup-composition-validation.test.ts +35 -0
  98. package/template/apps/server/src/startup-composition-validation.ts +54 -0
  99. package/template/apps/server/src/startup-preflight.test.ts +36 -0
  100. package/template/apps/server/src/startup-preflight.ts +37 -0
  101. package/template/apps/server/src/system/audit-routes.test.ts +9 -3
  102. package/template/apps/server/src/system/department-routes.test.ts +5 -3
  103. package/template/apps/server/src/system/feature-routes.test.ts +4 -3
  104. package/template/apps/server/src/system/interface-catalog.test.ts +5 -3
  105. package/template/apps/server/src/system/interface-catalog.ts +3 -0
  106. package/template/apps/server/src/system/menu-routes.test.ts +5 -3
  107. package/template/apps/server/src/system/route-test-runtime.ts +24 -0
  108. package/template/apps/server/src/system/routes-validation.test.ts +7 -3
  109. package/template/apps/server/src/system/routes.test.ts +21 -11
  110. package/template/apps/server/src/system/routes.ts +11 -2
  111. package/template/apps/server/src/work-order/foundation-modules.test.ts +6 -4
  112. package/template/apps/server/src/work-order/routes.test.ts +8 -0
  113. package/template/apps/server/src/work-order/task.test.ts +6 -1
  114. package/template/apps/web/Dockerfile +5 -0
  115. package/template/apps/web/package.json +1 -0
  116. package/template/apps/web/scripts/validate-admin-ui-composition.ts +106 -0
  117. package/template/apps/web/src/app/web-composition.ts +24 -0
  118. package/template/apps/web/src/app/web-composition.webtest.ts +15 -0
  119. package/template/apps/web/src/layout/AppSidebar.vue +8 -1
  120. package/template/apps/web/src/layout/NavigationSearchDialog.vue +8 -1
  121. package/template/apps/web/src/layout/navigation.ts +29 -4
  122. package/template/apps/web/src/layout/navigation.webtest.ts +51 -0
  123. package/template/apps/web/src/pages/dashboard/dashboard-presenter.ts +6 -1
  124. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.vue +202 -0
  125. package/template/apps/web/src/pages/work-orders/WorkOrderListPage.webtest.ts +51 -0
  126. package/template/apps/web/src/router/admin-extension-loader.ts +44 -0
  127. package/template/apps/web/src/router/admin-extension-loader.webtest.ts +49 -0
  128. package/template/apps/web/src/router/admin-ui-composition-plugin.ts +33 -0
  129. package/template/apps/web/src/router/admin-ui-composition-plugin.webtest.ts +20 -0
  130. package/template/apps/web/src/router/admin-ui-composition.ts +313 -0
  131. package/template/apps/web/src/router/admin-ui-composition.webtest.ts +225 -0
  132. package/template/apps/web/src/router/admin-ui-module-registry-plugin.ts +55 -0
  133. package/template/apps/web/src/router/admin-ui-module-registry-plugin.webtest.ts +46 -0
  134. package/template/apps/web/src/router/business-routes.ts +32 -45
  135. package/template/apps/web/src/router/business-routes.webtest.ts +40 -44
  136. package/template/apps/web/src/router/index.ts +64 -54
  137. package/template/apps/web/src/router/manifest-routes.ts +27 -7
  138. package/template/apps/web/src/router/manifest-routes.webtest.ts +10 -2
  139. package/template/apps/web/src/router/platform-admin-route-bindings.ts +58 -0
  140. package/template/apps/web/src/router/reserved-route-paths.ts +14 -0
  141. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +10 -4
  142. package/template/apps/web/src/router/web-composition-host.ts +20 -0
  143. package/template/apps/web/src/services/work-order-api.ts +16 -0
  144. package/template/apps/web/src/services/work-order-api.webtest.ts +21 -0
  145. package/template/apps/web/src/virtual-admin-ui-modules.d.ts +5 -0
  146. package/template/apps/web/vite.config.ts +22 -1
  147. package/template/docs/architecture/migration-bundle.md +10 -7
  148. package/template/docs/architecture/module-manifest-composition.md +85 -3
  149. package/template/docs/architecture/search-provider-protocol.md +6 -1
  150. package/template/docs/architecture/server-module-interface.md +70 -7
  151. package/template/docs/development/business-web-routes.md +53 -50
  152. package/template/docs/development/module-lifecycle.md +48 -54
  153. package/template/docs/platform/agent-runtime.md +2 -0
  154. package/template/docs/platform/navigation-registry.md +5 -4
  155. package/template/package.json +1 -1
  156. package/template/packages/config/package.json +1 -1
  157. package/template/packages/database/package.json +1 -1
  158. package/template/packages/example-work-order/package.json +6 -0
  159. package/template/packages/example-work-order/src/admin-page.ts +78 -0
  160. package/template/packages/example-work-order/src/admin.ts +35 -0
  161. package/template/packages/example-work-order/src/manifest.test.ts +62 -5
  162. package/template/packages/example-work-order/src/manifest.ts +54 -7
  163. package/template/packages/example-work-order/src/migration-bundle.ts +8 -0
  164. package/template/packages/example-work-order/src/server-module.ts +2 -2
  165. package/template/packages/jobs/package.json +1 -1
  166. package/template/packages/modules/index.ts +4 -0
  167. package/template/packages/modules/package.json +3 -2
  168. package/template/packages/modules/src/admin-ui-entry.ts +8 -0
  169. package/template/packages/modules/src/admin-ui-manifest.test.ts +52 -0
  170. package/template/packages/modules/src/admin-ui-manifest.ts +53 -0
  171. package/template/packages/modules/src/capability-audit-validation.test.ts +127 -0
  172. package/template/packages/modules/src/capability-audit-validation.ts +38 -0
  173. package/template/packages/modules/src/capability-guard-validation.ts +148 -0
  174. package/template/packages/modules/src/capability-key-validation.test.ts +150 -0
  175. package/template/packages/modules/src/composition-fixtures.test-support.ts +7 -5
  176. package/template/packages/modules/src/composition.test.ts +74 -9
  177. package/template/packages/modules/src/composition.ts +8 -0
  178. package/template/packages/modules/src/manifest.ts +1 -0
  179. package/template/packages/modules/src/migration-bundle-validation.test.ts +23 -0
  180. package/template/packages/modules/src/module-host-range.test.ts +57 -0
  181. package/template/packages/modules/src/module-host-range.ts +125 -0
  182. package/template/packages/modules/src/project-module-assembly.test.ts +133 -0
  183. package/template/packages/modules/src/project-module-assembly.ts +126 -0
  184. package/template/packages/modules/src/system-admin-routes.ts +6 -5
  185. package/template/packages/modules/src/system-admin-ui-catalog.ts +72 -0
  186. package/template/packages/modules/src/system-agent-tools.ts +2 -2
  187. package/template/packages/modules/src/system-api-binding.ts +43 -9
  188. package/template/packages/modules/src/system-api-control-plane.ts +202 -0
  189. package/template/packages/modules/src/system-api-data-governance.ts +4 -2
  190. package/template/packages/modules/src/system-api-operational.ts +57 -8
  191. package/template/packages/modules/src/system-api-permissions.ts +115 -21
  192. package/template/packages/modules/src/system-api-watermark.ts +3 -0
  193. package/template/packages/modules/src/system-audit-actions.ts +7 -0
  194. package/template/packages/modules/src/system-data-governance.ts +5 -2
  195. package/template/packages/modules/src/system-features.ts +31 -0
  196. package/template/packages/modules/src/system-governed-export-bindings.ts +8 -4
  197. package/template/packages/modules/src/system-module-catalog.test.ts +39 -4
  198. package/template/packages/modules/src/system-module-catalog.ts +68 -7
  199. package/template/packages/modules/src/system-module-tasks.ts +8 -1
  200. package/template/packages/modules/src/system-modules-access.test.ts +4 -0
  201. package/template/packages/modules/src/system-modules.test.ts +10 -2
  202. package/template/packages/modules/src/system-modules.ts +6 -5
  203. package/template/packages/modules/src/system-permissions.ts +4 -0
  204. package/template/packages/modules/user/module.ts +19 -2
  205. package/template/packages/server-sdk/index.ts +8 -0
  206. package/template/packages/server-sdk/package.json +2 -1
  207. package/template/packages/server-sdk/src/durable-handler.ts +16 -1
  208. package/template/packages/server-sdk/src/module-adapter.test.ts +44 -0
  209. package/template/packages/server-sdk/src/module-adapter.ts +59 -0
  210. package/template/packages/server-sdk/src/route-definition.ts +3 -2
  211. package/template/packages/shared/package.json +1 -1
  212. package/template/packages/shared/src/audit.test.ts +7 -0
  213. package/template/packages/shared/src/audit.ts +16 -0
  214. package/template/packages/shared/src/migration-bundle-validation.ts +9 -11
  215. package/template/packages/shared/src/rbac.ts +2 -0
  216. package/template/apps/web/src/app/business-routes.ts +0 -22
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-windy",
3
- "version": "0.3.18",
3
+ "version": "0.4.0",
4
4
  "description": "创建单组织、单租户、私有部署优先的 Windy 企业 Dashboard Starter",
5
5
  "type": "module",
6
6
  "bin": {
@@ -10,51 +10,75 @@ description: "Windy 业务模块、Manifest、Repository、Service、Agent Tool
10
10
 
11
11
  ## 工作流
12
12
 
13
- 1. 选择一个稳定的能力基础 Key 和模块命名空间。先定义业务边界、Actor、数据归属与
13
+ 1. 先运行 `windy module list` 读取项目实际装配;安装、启用、停用、升级或移除模块时,
14
+ 先执行对应命令的 `--dry-run`,再处理兼容范围、依赖、配置、权限与 Migration 提示。
15
+ 不直接修改 `node_modules`、`.windy` 升级元数据,也不为新增入口改写客户 Admin
16
+ layout。当前 CLI 对无法安全自动改变的 source/template 模块会 fail closed;按报告
17
+ 修改 project-owned Composition,不能伪造安装成功。
18
+ 2. 选择一个稳定的能力基础 Key 和模块命名空间。先定义业务边界、Actor、数据归属与
14
19
  失败语义,再改代码。
15
- 2. 在业务 package 的 Module Manifest 一次性注册 Feature、Permission、API、
20
+ 3. 在业务 package 的 Module Manifest 一次性注册 Feature、Permission、API、
16
21
  菜单/入口、审计动作,以及需要的 Task、Provider、JSON Schema 和 Tool。
17
- 3. 数据层使用 Drizzle Schema、前向 Migration 和 Repository;Service 持有业务规则,
22
+ Feature 直接使用能力基础 Key,Permission 在同一 Key 后追加动作;Route、Task、
23
+ Tool、Provider 与 Durable Handler 只能绑定这里已注册且基础 Key 一致的能力。
24
+ 4. 数据层使用 Drizzle Schema、前向 Migration 和 Repository;Service 持有业务规则,
18
25
  其它模块只能通过 Service、公开 Port 或模块注册机制协作。
19
- 4. Server 通过 `InstallableServerModule` 和 `ModuleHost` 注册 Guarded Route、Task、
26
+ 5. Server 通过 `InstallableServerModule` 和 `ModuleHost` 注册 Guarded Route、Task、
20
27
  Provider 或 Tool Handler。Handler 调用 Service,不直接访问其它模块 Repository。
21
28
  需要选择启用用户时使用 `ModuleRequestContext.userDirectory`;不要导入用户 Admin
22
29
  Repository。业务 RolePreset 不能注册平台 Permission,部署管理员须显式向业务角色
23
30
  授予 `system.user-directory.read` 并配置数据范围。
24
- 5. Tool 输入输出只使用 Manifest 已注册的顶层 object JSON Schema。Actor、Permission、
31
+ 6. Tool 输入输出只使用 Manifest 已注册的顶层 object JSON Schema。Actor、Permission、
25
32
  Feature、License 和 Data Scope 只来自 `ModuleToolContext` 与平台 Runtime。
26
33
  Tool 或 Route 需要读取同业务范围内其他用户上传的文件时,在 Host 注册精确
27
34
  `purpose + resourceType` 的 `registerResourceFileAccessPolicies()`;Resolver 必须用
28
35
  project-owned Repository 回查资源与 fileId 绑定及对象级授权,只返回布尔判定,
29
36
  再通过 `context.storage.openResourceFile()` 取得最小元数据与流。禁止注入
30
37
  UploadRepository、BlobStore 或申请平台 manage Repository。
31
- 6. 业务资源包含敏感字段时,在 Manifest 一次声明 Data Policy、standard read、
38
+ 7. 业务资源包含敏感字段时,在 Manifest 一次声明 Data Policy、standard read、
32
39
  sensitive-disclosure reveal、governed-export Permission 与 masked/reveal API 绑定,
33
40
  再用 `registerGovernedResources()` 分别注册对象授权 Resolver 和去除治理字段的投影。
34
41
  需要导出时再登记四个 Manifest API 绑定、受控查询校验和 cursor 分页 Source;审批、
35
42
  Durable Job、水印、TTL、下载与取消由宿主负责。业务不自行签发明文 Grant、校验密码,
36
43
  也不接触 Workflow、BlobStore 或平台 Repository。
37
- 7. 若项目包含 `system.agent`,`operation=query` 的 Tool 会由同一 Registry 自动进入
44
+ 8. 若项目包含 `system.agent`,`operation=query` 的 Tool 会由同一 Registry 自动进入
38
45
  MCP;不要编辑 MCP Adapter 或复制 Tool 定义。写操作在幂等、审批和结果追踪闭环前
39
46
  不通过 MCP 暴露。
40
47
  对无需 Actor、Repository、Secret、文件系统或外网的纯计算 Tool,可改用
41
48
  `setupSandbox()` 注册;此入口必须同步且无副作用,Handler 只能使用输入、
42
49
  `idempotencyKey` 与 `AbortSignal`。需要业务数据或外部服务的 Tool 必须保留在普通
43
50
  `setup()`,不能为了使用 Sandbox 绕过依赖边界。
44
- 8. 补 Manifest/Composition、Repository/Service、Guard 拒绝和 Handler 契约测试。
51
+ 9. 补 Manifest/Composition、Repository/Service、Guard 拒绝和 Handler 契约测试。
52
+ 涉及 Durable Handler 时覆盖 Feature/Permission/Audit 绑定,并证明首次执行、重试和
53
+ 恢复都会按当前 License、Feature 与 RBAC 状态重新授权。
45
54
  涉及 Tool 时还要覆盖 Schema 拒绝、数据范围与未授权发现/调用;涉及可选模块时覆盖
46
55
  Starter 的文件、依赖和数据库裁剪。涉及资源文件时还要覆盖跨上传者成功、资源绑定/
47
56
  purpose/Feature/Permission/Scope 任一拒绝、最小元数据与成功/拒绝审计。
48
57
  涉及 Sandbox Tool 时还要覆盖无网络、无平台 Secret、无宿主数据挂载、Worker
49
58
  重启恢复,以及策略拒绝与业务失败审计分类。
50
- 9. 更新长期文档,运行受影响测试、`bun run typecheck` 和 `bun run lint`,按一个完整
51
- 小切片提交。
59
+ 10. 更新长期文档,运行受影响测试、`bun run typecheck` 和 `bun run lint`,按一个完整
60
+ 小切片提交。
61
+
62
+ ## 项目所有权与自动装配
63
+
64
+ - `apps/web/src`、`apps/server/src/app`、业务模块源码和
65
+ `windy.modules.json` 都是 project-owned;客户可以修改或替换。日常模块命令只更新
66
+ package/锁文件与装配声明,不覆盖 Admin、Router、layout 或 Server Composition。
67
+ - Admin Host 从启用模块的 `./admin` 与 Manifest 贡献投影页面、入口和可见性。客户可
68
+ 隐藏、排序或替换展示;模块不得直接写侧边栏源码。
69
+ - Server Host 只接受 canonical `./manifest`、声明兼容范围的 Adapter 和显式
70
+ attachment。动态 package 不获得数据库或平台 Repository;需要项目 Repository 的
71
+ 模块在 project-owned Composition 中装配。
72
+ - 每次安装、构建和 Server 监听前都必须执行同一套 Manifest/实现双向校验。未知、
73
+ 重复、缺失声明以及 setup 后替换或变异 canonical Manifest 都应 fail closed。
52
74
 
53
75
  ## 代码事实源
54
76
 
55
77
  - Manifest 与 Tool 类型:`packages/modules/src/manifest.ts`
56
78
  - Tool 组合校验:`packages/modules/src/tool-composition.ts`
57
79
  - 模块 Host 契约:`packages/server-sdk/src/module-host.ts`
80
+ - Durable Handler 契约:`packages/server-sdk/src/durable-handler.ts`
81
+ - 项目模块生命周期:`packages/create-windy/src/module-lifecycle-resolver.ts`
58
82
  - Tool Handler 契约:`packages/server-sdk/src/tool-registration.ts`
59
83
  - Sandbox Tool 契约:`packages/server-sdk/src/sandbox-tool.ts`
60
84
  - 可移除业务示例:`packages/example-work-order/`、`apps/server/src/work-order/`
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "templateCommit": "f1470acc1588deb087d3144a76b0729affed0988",
4
- "generatorVersion": "0.3.18",
3
+ "templateCommit": "50d32b9470dbf55110aba320338f5e54ed896c77",
4
+ "generatorVersion": "0.4.0",
5
5
  "modules": [
6
6
  {
7
7
  "name": "system",
@@ -40,20 +40,30 @@
40
40
  共享脚手架或根工具链同样受支持,但通常会增加后续三方合并成本。
41
41
  - 修改前可查看 ownership 评估升级风险;优先扩展 `project-owned` 路径,对
42
42
  `windy-managed` 路径建议保持改动小而清晰。
43
+ - `windy.modules.json` 是 project-owned 模块装配选择:可调整启用、顺序、Admin
44
+ 可见性与模块配置;package 只写无版本入口,实际版本以 `package.json` 和
45
+ `bun.lock` 为准。日常模块命令不得为了装配入口改写客户 Router、layout 或 Host。
46
+ - 整个 `apps/web/src` 与整个 `apps/server/src/app` 都是 project-owned。普通
47
+ `windy update` 不覆盖或三方合并客户的 Admin、Router、layout、页面、Host 或
48
+ Server 组合。
49
+ - 当前没有 H3 模块,也不支持任意 npm package 名称安装。日常只使用
50
+ `module list/enable/disable`;内置模块的 add/update/remove 实际集合变化会在写入前
51
+ fail closed。`system.settings`、`system.storage` 与
52
+ `system.data-governance` 不能只靠 registry 安全停用。
43
53
  - 直接修改 Windy 基线代码时,使用独立小提交记录原因和影响;升级前先运行
44
54
  `bun run windy update --dry-run`,根据三方合并计划处理冲突。
45
- - 完整定制登录页时,在 project-owned 的
46
- `apps/web/src/app/auth/login-page.ts` 注册业务组件可获得最低升级成本。若直接修改
47
- `apps/web/src/pages/auth/LoginPage.vue` 或 Router,升级器会尝试三方合并。业务页面复用
55
+ - 完整定制登录页时,可在 `apps/web/src/app/auth/login-page.ts` 注册业务组件,也可
56
+ 直接修改 `apps/web/src/pages/auth/LoginPage.vue` 或 Router;这些文件均由项目拥有。
57
+ 业务页面复用
48
58
  `useLoginFlow`、`DevelopmentAuthWarning` 和平台品牌设置,保留安全
49
59
  `returnTo`、真实认证与开发认证提示。
50
- - 定制 404、500、维护中或网络异常页时,优先修改 project-owned 的
60
+ - 定制 404、500、维护中或网络异常页时,可以修改
51
61
  `apps/web/src/app/error-pages.ts`,并把业务 SFC 放在 `apps/web/src/app/`;
52
- 直接修改 Router、HTTP 核心或平台默认错误页时会增加三方合并成本。
62
+ 也可以直接修改项目拥有的 Router 或平台默认错误页。
53
63
  - 新增业务管理页面时,在业务 Module Manifest 注册 Feature、Permission、菜单和
54
- Admin Route,再把 Vue 页面绑定到 project-owned 的
55
- `apps/web/src/app/business-routes.ts` 是推荐接入方式。直接修改 Router 或 AppSidebar
56
- 时同样按 Base/Ours/Theirs 合并。导航来自服务端 Manifest 快照,接入见
64
+ Admin Route,并由 package 的 `./admin` 提供默认页面;项目需要替换页面时在
65
+ `apps/web/src/app/web-composition.ts` 按 route key 覆盖 loader。业务工作台路由可在
66
+ 项目拥有的 Router 中直接实现。导航来自服务端 Manifest 快照,接入见
57
67
  `docs/development/business-web-routes.md`。
58
68
  - 业务 Agent Operation 开启 Provider 出网时,必须在 Manifest 同时声明最小
59
69
  `providerDataPolicy.inputFields` JSON Pointer allowlist 与
@@ -62,9 +62,10 @@ Skill 完成该能力”。Skill 只指导开发流程;Feature、Permission、
62
62
  运行时事实仍以 Module Manifest 与平台 Registry 为准。
63
63
 
64
64
  当前组合未包含 `system.agent`,MCP Server、Agent Tool Host 和相关文档已被
65
- 物理裁剪。创建时可增加 `--module system.agent` 或使用 `--all-modules`;已部署
66
- 项目使用 `bun run windy module add system.agent`,不要手工复制 MCP Adapter 或
67
- 另建授权链。仓库 Skill 仍可用于普通业务模块开发。
65
+ 物理裁剪。创建时可增加 `--module system.agent` 或使用 `--all-modules`。当前没有
66
+ self-contained H3 模块,已部署项目当前不能通过模块命令补装 `system.agent`;
67
+ `module add` 会在写入前 fail closed。不要手工复制 MCP Adapter 或另建授权链。仓库
68
+ Skill 仍可用于普通业务模块开发。
68
69
 
69
70
 
70
71
 
@@ -101,8 +102,8 @@ create-windy 在本机 `.env` 中随机生成的 `WINDY_BOOTSTRAP_ADMIN_PASSWORD
101
102
  | 法律许可 | `UNLICENSED` |
102
103
 
103
104
  平台当前注册的全部模块如下。H0 表示 profile 锁定核心;H2 表示创建时可以物理包含或
104
- 排除;H3 还具备部署期安装、更新、数据保留卸载和重装证据。通用生命周期命令会保守
105
- 处理所有目录模块,但只有通过完整 H3 回归的模块才标记为 H3。
105
+ 排除;H3 还具备部署期安装、更新、数据保留卸载和重装证据。当前没有 H3 模块,也不
106
+ 支持任意 npm package 名称安装。
106
107
 
107
108
  | Module Key | 当前项目 | 热插拔可靠度 | 说明 |
108
109
  | ---------- | -------- | ------------ | ---- |
@@ -121,17 +122,24 @@ create-windy 在本机 `.env` 中随机生成的 `WINDY_BOOTSTRAP_ADMIN_PASSWORD
121
122
  | `system.bulk-data` | 未包含 | H2 | 批量导入导出与结果留存。 |
122
123
  | `system.search` | 未包含 | H2 | 权限感知搜索与 Provider 聚合。 |
123
124
  | `system.data-governance` | 未包含 | H2 | 敏感字段、明文访问和治理导出。 |
124
- | `work-order` | 未包含 | H3 | 可替换的业务模块接入示例。 |
125
-
126
- 项目自有业务模块以 `apps/server/src/installed-business-modules.ts` 装配的 Module Manifest
127
- 为准;对应 Vue 页面在 project-owned 的
128
- `apps/web/src/app/business-routes.ts` 注册,侧栏继续从服务端 Manifest 快照生成。
129
- 不要为增加业务页面修改 shared-scaffold Router 或 AppSidebar。shared-scaffold README
130
- 不复制项目业务 Key,避免平台升级固化产品命名。
131
-
132
- 精确生成来源和选择记录在 `.windy/generation.json`。当前版本已提供可回滚的
133
- `windy update` 与 `windy module list/add/update/remove`。Feature Flag 关闭仍不
134
- 等于模块卸载。
125
+ | `work-order` | 未包含 | H2 | 可替换的业务模块接入示例。 |
126
+
127
+ 项目装配选择记录在 project-owned 的 `windy.modules.json`,可提交 Git 并结构化编辑;
128
+ 它只保存无版本 package 入口、启用状态、顺序、Admin 可见性与模块配置。依赖版本事实
129
+ 仍只来自 `package.json` 与 `bun.lock`。Server 默认从
130
+ `apps/server/src/app/server-composition.ts` 装配 Adapter;Web 通过 package 的
131
+ browser-safe `./admin` 与 project-owned 的
132
+ `apps/web/src/app/web-composition.ts` 装配页面,不复制第二份 Feature、Permission
133
+ 或路由清单。整个 `apps/web/src` 与整个 `apps/server/src/app` 都是
134
+ project-owned,客户可以修改 Router、layout、页面、Host 和 Server 组合;普通
135
+ `windy update` 不会覆盖或三方合并这些文件。
136
+
137
+ 精确生成来源保留在 `.windy/generation.json`。当前版本已提供可回滚的
138
+ `windy update` 与 `windy module list/enable/disable`。当前内置模块仍依赖
139
+ source/template 或 project-owned Composition;当前内置模块的 add/update/remove
140
+ 实际集合变化会在写入前 fail closed。Feature Flag 关闭、module disable 与物理卸载是
141
+ 三个不同动作;`system.settings`、`system.storage` 和
142
+ `system.data-governance` 只能在创建期物理排除,部署后不能只改 registry 安全停用。
135
143
 
136
144
  ## 常用命令
137
145
 
@@ -148,8 +156,7 @@ bun run windy doctor
148
156
  bun run windy update --check
149
157
  bun run windy update --dry-run
150
158
  bun run windy module list
151
- bun run windy module add work-order --dry-run
152
- bun run windy module remove work-order --dry-run
159
+ # 当前组合没有可安全 runtime disable 的已安装可选模块
153
160
  bun run lint
154
161
  bun run format
155
162
  ```
@@ -158,7 +165,9 @@ bun run format
158
165
 
159
166
  更新前无需提交、暂存或清理业务改动。`--dry-run` 只生成计划,不写项目;无关 WIP
160
167
  保持原样,命中同一路径时按冻结快照与三方合并 fail closed。正式更新先备份,再做
161
- package 字段级合并、共享脚手架三方合并与完整验证。任何冲突都会在写入前中止,
168
+ package 字段级合并、共享脚手架三方合并与完整验证。整个 `apps/web/src` 和
169
+ `apps/server/src/app` 保持项目版本。任何冲突都会在写入前中止,模块启停在交互
170
+ 终端确认;CI、Agent 或其它非 TTY 环境必须显式传入 `--yes`。
162
171
  无并发变化的验证失败会自动回滚;中断恢复使用
163
172
  `bun run windy doctor --repair`。该命令在提交点前保留并发编辑并回滚其余路径,
164
173
  在 committed 提交点后只完成事务状态与备份清理,不回滚项目文件。
@@ -181,9 +190,10 @@ create-windy 默认建立 `main` 分支并创建 `chore: initialize project` 首
181
190
  没有配置 Git 用户身份,文件会保留在暂存区;配置身份后手动提交即可。
182
191
 
183
192
  当前已交付的 Windy 更新器会根据 Project Lock 和文件所有权,从 Project Lock 指向的
184
- 精确 npm artifact 重建 Base 并执行三方合并;它不连接私有源仓库,也不会静默覆盖项目代码。模块
185
- 生命周期命令复用同一计划、事务、验证与恢复边界;卸载默认保留数据库数据,并要求
186
- 显式确认数据处置计划。
193
+ 精确 npm artifact 重建 Base 并执行三方合并;它不连接私有源仓库。
194
+ 更新器不会静默覆盖项目代码。`module enable/disable` 只以 CAS 事务更新
195
+ `windy.modules.json`;当前内置模块的 add/update/remove 实际集合变化在生成更新
196
+ 计划前即被拒绝。
187
197
 
188
198
  ## License
189
199
 
@@ -1,5 +1,6 @@
1
1
  from __future__ import annotations
2
2
 
3
+ import asyncio
3
4
  import json
4
5
  import os
5
6
  import uuid
@@ -15,6 +16,8 @@ from .config import normalize_base_url
15
16
  MAX_QUESTION_LENGTH = 4_000
16
17
  MAX_VISIBLE_EVENTS = 64
17
18
  MAX_EVENT_FRAME_BYTES = 64 * 1024
19
+ DEFAULT_RESULT_POLL_TIMEOUT_SECONDS = 60.0
20
+ DEFAULT_RESULT_POLL_INTERVAL_SECONDS = 0.5
18
21
 
19
22
 
20
23
  @dataclass(frozen=True)
@@ -99,11 +102,19 @@ class CanonicalRunDebugClient:
99
102
  bearer_token: str,
100
103
  *,
101
104
  transport: httpx.AsyncBaseTransport | None = None,
105
+ result_poll_timeout_seconds: float = DEFAULT_RESULT_POLL_TIMEOUT_SECONDS,
106
+ result_poll_interval_seconds: float = DEFAULT_RESULT_POLL_INTERVAL_SECONDS,
102
107
  ) -> None:
103
108
  self._operation_key = operation_key
104
109
  self._base_url = normalize_base_url(base_url)
105
110
  self._bearer_token = bearer_token.strip()
106
111
  self._transport = transport
112
+ if not 0 <= result_poll_timeout_seconds <= 120:
113
+ raise ValueError("result_poll_timeout_seconds 必须在 0 到 120 秒之间")
114
+ if not 0 < result_poll_interval_seconds <= 1:
115
+ raise ValueError("result_poll_interval_seconds 必须大于 0 且不超过 1 秒")
116
+ self._result_poll_timeout_seconds = result_poll_timeout_seconds
117
+ self._result_poll_interval_seconds = result_poll_interval_seconds
107
118
  if not self._bearer_token:
108
119
  raise RuntimeError("ADK_DEBUG_PLATFORM_TOKEN 未配置,拒绝启动真实调试 Run")
109
120
 
@@ -142,13 +153,35 @@ class CanonicalRunDebugClient:
142
153
  )
143
154
  run_id = read_run_id(started)
144
155
  events = await collect_compact_events(self._events(client, run_id))
145
- result = await client.get(
146
- f"api/agent/runs/{run_id}/result",
147
- params={"operationKey": self._operation_key},
148
- )
156
+ try:
157
+ async with asyncio.timeout(self._result_poll_timeout_seconds):
158
+ while True:
159
+ result = await client.get(
160
+ f"api/agent/runs/{run_id}/result",
161
+ params={"operationKey": self._operation_key},
162
+ )
163
+ if result.is_success:
164
+ break
165
+ try:
166
+ error = result.json()
167
+ except json.JSONDecodeError:
168
+ error = None
169
+ if (
170
+ result.status_code != 409
171
+ or not isinstance(error, dict)
172
+ or error.get("code") != "RUN_CONFLICT"
173
+ or error.get("retryable") is not True
174
+ ):
175
+ raise RuntimeError("平台 Agent Run 永久失败或结果读取失败")
176
+ await asyncio.sleep(self._result_poll_interval_seconds)
177
+ except TimeoutError as error:
178
+ raise RuntimeError("平台 Agent Run 结果等待超时") from error
179
+ answer = read_text_output(result, run_id)
180
+ if not any(event.get("type") == "run.completed" for event in events):
181
+ events = [*events[: MAX_VISIBLE_EVENTS - 1], {"type": "run.completed"}]
149
182
  return {
150
183
  "runId": run_id,
151
- "answer": read_text_output(result, run_id),
184
+ "answer": answer,
152
185
  "events": events,
153
186
  }
154
187
 
@@ -0,0 +1,139 @@
1
+ from __future__ import annotations
2
+
3
+ import asyncio
4
+
5
+ import httpx
6
+ import pytest
7
+
8
+ from southwind_agent_server.adk_debug import CanonicalRunDebugClient
9
+
10
+ OPERATION_KEY = "customer.copilot.global"
11
+
12
+
13
+ async def test_client_waits_for_result_after_stream_closes() -> None:
14
+ result_attempts = 0
15
+ started_runs = 0
16
+
17
+ async def handler(request: httpx.Request) -> httpx.Response:
18
+ nonlocal result_attempts, started_runs
19
+ if request.url.path == "/api/agent/runs":
20
+ started_runs += 1
21
+ return httpx.Response(200, json={"runId": "run_race"})
22
+ if request.url.path.endswith("/events"):
23
+ return httpx.Response(
24
+ 200,
25
+ content=(
26
+ b'event: run.started\ndata: {"sequence":1,"type":"run.started"}\n\n'
27
+ ),
28
+ )
29
+ if request.url.path.endswith("/result"):
30
+ result_attempts += 1
31
+ if result_attempts < 3:
32
+ return httpx.Response(
33
+ 409,
34
+ json={"code": "RUN_CONFLICT", "retryable": True},
35
+ )
36
+ return httpx.Response(
37
+ 200,
38
+ json={
39
+ "runId": "run_race",
40
+ "status": "completed",
41
+ "output": {"text": "稍后可读"},
42
+ },
43
+ )
44
+ raise AssertionError(request.url)
45
+
46
+ client = CanonicalRunDebugClient(
47
+ OPERATION_KEY,
48
+ "https://platform.test",
49
+ "developer-token",
50
+ transport=httpx.MockTransport(handler),
51
+ result_poll_interval_seconds=0.001,
52
+ )
53
+
54
+ result = await client.run("验证竞态")
55
+
56
+ assert result["answer"] == "稍后可读"
57
+ events = result["events"]
58
+ assert isinstance(events, list) and {"type": "run.completed"} in events
59
+ assert started_runs == 1
60
+ assert result_attempts == 3
61
+
62
+
63
+ @pytest.mark.parametrize(
64
+ ("payload", "timeout_seconds", "delay_seconds", "message"),
65
+ [
66
+ (
67
+ {
68
+ "code": "RUN_CANCELLED",
69
+ "message": "secret-upstream-body",
70
+ "retryable": False,
71
+ },
72
+ 5,
73
+ 0,
74
+ "永久失败",
75
+ ),
76
+ (
77
+ {
78
+ "code": "RUN_CONFLICT",
79
+ "message": "Agent Run 尚未完成",
80
+ "retryable": True,
81
+ },
82
+ 0.01,
83
+ 0,
84
+ "等待超时",
85
+ ),
86
+ (
87
+ {
88
+ "code": "RUN_CONFLICT",
89
+ "message": "slow-secret-upstream-body",
90
+ "retryable": True,
91
+ },
92
+ 0.01,
93
+ 0.05,
94
+ "等待超时",
95
+ ),
96
+ ],
97
+ )
98
+ async def test_client_bounds_result_polling_and_hides_error_content(
99
+ payload: dict[str, object],
100
+ timeout_seconds: float,
101
+ delay_seconds: float,
102
+ message: str,
103
+ ) -> None:
104
+ requests: list[httpx.Request] = []
105
+
106
+ async def handler(request: httpx.Request) -> httpx.Response:
107
+ requests.append(request)
108
+ if request.url.path == "/api/agent/runs":
109
+ return httpx.Response(200, json={"runId": "run_terminal"})
110
+ if request.url.path.endswith("/events"):
111
+ return httpx.Response(
112
+ 200,
113
+ content=(
114
+ b"event: run.completed\ndata: "
115
+ b'{"sequence":1,"type":"run.completed"}\n\n'
116
+ ),
117
+ )
118
+ if request.url.path.endswith("/result"):
119
+ await asyncio.sleep(delay_seconds)
120
+ return httpx.Response(409, json=payload)
121
+ raise AssertionError(request.url)
122
+
123
+ client = CanonicalRunDebugClient(
124
+ OPERATION_KEY,
125
+ "https://platform.test",
126
+ "developer-token",
127
+ transport=httpx.MockTransport(handler),
128
+ result_poll_timeout_seconds=timeout_seconds,
129
+ result_poll_interval_seconds=0.001,
130
+ )
131
+
132
+ with pytest.raises(RuntimeError, match=message) as failure:
133
+ await client.run("验证结果失败")
134
+
135
+ error_message = str(failure.value)
136
+ assert "secret-upstream-body" not in error_message
137
+ assert "slow-secret-upstream-body" not in error_message
138
+ assert sum(request.url.path == "/api/agent/runs" for request in requests) == 1
139
+ assert sum(request.url.path.endswith("/result") for request in requests) >= 1
@@ -4,6 +4,7 @@ FROM oven/bun:1.3.14-alpine AS deps
4
4
  WORKDIR /app
5
5
 
6
6
  COPY package.json bun.lock ./
7
+ COPY windy.modules.json windy.modules.json
7
8
  # windy-copy:deps begin
8
9
  COPY apps/license/package.json apps/license/package.json
9
10
  COPY apps/server/package.json apps/server/package.json
@@ -4,6 +4,7 @@
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
7
+ "build": "bun run scripts/validate-module-composition.ts",
7
8
  "dev": "bun --watch run src/index.ts",
8
9
  "start": "bun run src/index.ts",
9
10
  "typecheck": "tsc --noEmit --project tsconfig.json"
@@ -0,0 +1,4 @@
1
+ process.argv.push("--validate-module-composition");
2
+ delete process.env.DATABASE_URL;
3
+
4
+ await import("../src/index.js");
@@ -0,0 +1,28 @@
1
+ import { spawnSync } from "node:child_process";
2
+
3
+ const variants = [
4
+ { name: "default", env: { PLATFORM_ENABLE_AI: "false" } },
5
+ {
6
+ name: "ai-enabled",
7
+ env: {
8
+ PLATFORM_ENABLE_AI: "true",
9
+ AGENT_SERVER_INTERNAL_TOKEN: "0123456789abcdef0123456789abcdef",
10
+ AI_ARTIFACT_ENCRYPTION_SECRET: "abcdef0123456789abcdef0123456789",
11
+ },
12
+ },
13
+ ] as const;
14
+
15
+ for (const variant of variants) {
16
+ const result = spawnSync(
17
+ process.execPath,
18
+ ["run", "scripts/validate-module-composition-once.ts"],
19
+ {
20
+ cwd: process.cwd(),
21
+ env: { ...process.env, ...variant.env, DATABASE_URL: "" },
22
+ stdio: "inherit",
23
+ },
24
+ );
25
+ if (result.status !== 0) {
26
+ throw new Error(`${variant.name} Module Composition 验证失败`);
27
+ }
28
+ }
@@ -2,6 +2,7 @@ import { describe, expect, test } from "bun:test";
2
2
  import { buildScopeContext } from "../data-access/context.js";
3
3
  import { createFoundationSnapshot, loadSystemModules } from "../foundation.js";
4
4
  import { createServerRuntime } from "../runtime.js";
5
+ import { createStaticRouteAuthorizer } from "../module-host/static-route-authorization.js";
5
6
  import { FakeRouteApp } from "../system/route-test-app.js";
6
7
  import { createSystemRepositories } from "../system/seed.js";
7
8
  import {
@@ -13,7 +14,11 @@ import { GovernedToolRegistry } from "./tool-registry.js";
13
14
 
14
15
  describe("Agent Tool data scope", () => {
15
16
  test("启动时拒绝 Manifest 与 Handler 不一致", () => {
16
- const runtime = createServerRuntime({ WINDY_DEV_ADMIN_TOKEN: "token_1" });
17
+ const snapshot = createFoundationSnapshot();
18
+ const runtime = createServerRuntime(
19
+ { WINDY_DEV_ADMIN_TOKEN: "token_1" },
20
+ { featureCatalog: snapshot.features },
21
+ );
17
22
  const handlers = createSystemToolHandlers(
18
23
  createSystemRepositories(createFoundationSnapshot()),
19
24
  runtime,
@@ -41,8 +46,12 @@ describe("Agent Tool data scope", () => {
41
46
  test("用户 Tool 忽略客户端 ownership 参数并使用 Server self scope", async () => {
42
47
  const snapshot = createFoundationSnapshot();
43
48
  const repositories = createSystemRepositories(snapshot);
44
- const runtime = createServerRuntime({ WINDY_DEV_ADMIN_TOKEN: "token_1" });
49
+ const runtime = createServerRuntime(
50
+ { WINDY_DEV_ADMIN_TOKEN: "token_1" },
51
+ { featureCatalog: snapshot.features },
52
+ );
45
53
  const app = new FakeRouteApp();
54
+ const modules = loadSystemModules();
46
55
  const guardContext = await runtime.createGuardContext(
47
56
  authenticatedRequest(),
48
57
  );
@@ -64,10 +73,11 @@ describe("Agent Tool data scope", () => {
64
73
  registerAgentRoutes(
65
74
  app,
66
75
  new GovernedToolRegistry(
67
- loadSystemModules(),
76
+ modules,
68
77
  createSystemToolHandlers(repositories, runtime),
69
78
  runtime,
70
79
  ),
80
+ createStaticRouteAuthorizer(runtime, modules),
71
81
  );
72
82
  guardContext.actor = {
73
83
  ...guardContext.actor,
@@ -94,15 +104,20 @@ describe("Agent Tool data scope", () => {
94
104
 
95
105
  test("请求级 Tool 缺少 Server ScopeContext 时 fail closed", async () => {
96
106
  const snapshot = createFoundationSnapshot();
97
- const runtime = createServerRuntime({ WINDY_DEV_ADMIN_TOKEN: "token_1" });
107
+ const runtime = createServerRuntime(
108
+ { WINDY_DEV_ADMIN_TOKEN: "token_1" },
109
+ { featureCatalog: snapshot.features },
110
+ );
98
111
  const app = new FakeRouteApp();
112
+ const modules = loadSystemModules();
99
113
  registerAgentRoutes(
100
114
  app,
101
115
  new GovernedToolRegistry(
102
- loadSystemModules(),
116
+ modules,
103
117
  createSystemToolHandlers(createSystemRepositories(snapshot), runtime),
104
118
  runtime,
105
119
  ),
120
+ createStaticRouteAuthorizer(runtime, modules),
106
121
  );
107
122
  const guardContext = await runtime.createGuardContext(
108
123
  authenticatedRequest(),
@@ -5,6 +5,7 @@ import {
5
5
  } from "@southwind-ai/shared";
6
6
  import { createFoundationSnapshot, loadSystemModules } from "../foundation.js";
7
7
  import { createServerRuntime } from "../runtime.js";
8
+ import { createStaticRouteAuthorizer } from "../module-host/static-route-authorization.js";
8
9
  import type { RouteApp } from "../system/routes.js";
9
10
  import { createSystemRepositories } from "../system/seed.js";
10
11
  import { createSystemToolHandlers, registerAgentRoutes } from "./routes.js";
@@ -37,12 +38,18 @@ class FakeRouteApp implements RouteApp {
37
38
  }
38
39
 
39
40
  function routeFixture(features?: FeatureFlagDefinition[]) {
40
- const runtime = createServerRuntime({ WINDY_DEV_ADMIN_TOKEN: "token_1" });
41
41
  const app = new FakeRouteApp();
42
- const modules = loadSystemModules();
43
- if (features) {
44
- modules[0] = { ...modules[0]!, features };
45
- }
42
+ const overrides = new Map(features?.map((feature) => [feature.key, feature]));
43
+ const modules = loadSystemModules().map((module) => ({
44
+ ...module,
45
+ features: module.features.map(
46
+ (feature) => overrides?.get(feature.key) ?? feature,
47
+ ),
48
+ }));
49
+ const runtime = createServerRuntime(
50
+ { WINDY_DEV_ADMIN_TOKEN: "token_1" },
51
+ { featureCatalog: modules.flatMap((module) => module.features) },
52
+ );
46
53
  registerAgentRoutes(
47
54
  app,
48
55
  new GovernedToolRegistry(
@@ -53,6 +60,7 @@ function routeFixture(features?: FeatureFlagDefinition[]) {
53
60
  ),
54
61
  runtime,
55
62
  ),
63
+ createStaticRouteAuthorizer(runtime, modules),
56
64
  );
57
65
  return { app, runtime };
58
66
  }
@@ -189,16 +197,21 @@ describe("agent routes", () => {
189
197
  await repositories.features.syncManifest(snapshot.features);
190
198
  const runtime = createServerRuntime(
191
199
  { WINDY_DEV_ADMIN_TOKEN: "token_1" },
192
- { featureResolver: repositories.features },
200
+ {
201
+ featureCatalog: snapshot.features,
202
+ featureResolver: repositories.features,
203
+ },
193
204
  );
194
205
  const app = new FakeRouteApp();
206
+ const modules = loadSystemModules();
195
207
  registerAgentRoutes(
196
208
  app,
197
209
  new GovernedToolRegistry(
198
- loadSystemModules(),
210
+ modules,
199
211
  createSystemToolHandlers(repositories, runtime),
200
212
  runtime,
201
213
  ),
214
+ createStaticRouteAuthorizer(runtime, modules),
202
215
  );
203
216
  const request = () =>
204
217
  new Request(