create-windy 0.2.33 → 0.3.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 (284) hide show
  1. package/dist/cli.js +173 -17
  2. package/package.json +1 -1
  3. package/template/.agents/skills/windy-business-module/SKILL.md +10 -1
  4. package/template/.windy-template.json +2 -2
  5. package/template/AGENTS.md +9 -0
  6. package/template/README.md +3 -1
  7. package/template/apps/agent-server/Dockerfile +1 -0
  8. package/template/apps/agent-server/README.md +28 -5
  9. package/template/apps/agent-server/adk_web_agents/__init__.py +1 -0
  10. package/template/apps/agent-server/adk_web_agents/safe_debug/__init__.py +3 -0
  11. package/template/apps/agent-server/adk_web_agents/safe_debug/agent.py +40 -0
  12. package/template/apps/agent-server/pyproject.toml +1 -1
  13. package/template/apps/agent-server/src/southwind_agent_server/adk_adapter.py +191 -16
  14. package/template/apps/agent-server/src/southwind_agent_server/adk_failure.py +13 -0
  15. package/template/apps/agent-server/src/southwind_agent_server/adk_model.py +225 -0
  16. package/template/apps/agent-server/src/southwind_agent_server/adk_tools.py +76 -0
  17. package/template/apps/agent-server/src/southwind_agent_server/app.py +63 -3
  18. package/template/apps/agent-server/src/southwind_agent_server/contracts.py +35 -0
  19. package/template/apps/agent-server/src/southwind_agent_server/engine.py +2 -0
  20. package/template/apps/agent-server/src/southwind_agent_server/openai_engine.py +31 -3
  21. package/template/apps/agent-server/src/southwind_agent_server/openai_provider.py +22 -9
  22. package/template/apps/agent-server/src/southwind_agent_server/provider_audit.py +144 -0
  23. package/template/apps/agent-server/src/southwind_agent_server/provider_governance.py +227 -0
  24. package/template/apps/agent-server/src/southwind_agent_server/service.py +1 -0
  25. package/template/apps/agent-server/tests/test_adk_adapter.py +305 -0
  26. package/template/apps/agent-server/tests/test_adk_web_agent.py +12 -0
  27. package/template/apps/agent-server/tests/test_api.py +16 -1
  28. package/template/apps/agent-server/tests/test_openai_provider.py +79 -0
  29. package/template/apps/agent-server/tests/test_provider_governance.py +215 -0
  30. package/template/apps/server/package.json +4 -1
  31. package/template/apps/server/src/agent/provider-input-governance.test.ts +82 -0
  32. package/template/apps/server/src/agent/provider-input-governance.ts +140 -0
  33. package/template/apps/server/src/agent/remote-runtime.test.ts +22 -0
  34. package/template/apps/server/src/agent/remote-runtime.ts +1 -0
  35. package/template/apps/server/src/agent/run-contracts.ts +2 -0
  36. package/template/apps/server/src/agent/run-facade.test.ts +52 -2
  37. package/template/apps/server/src/agent/run-facade.ts +38 -9
  38. package/template/apps/server/src/agent/tool-registry.ts +2 -6
  39. package/template/apps/server/src/application-services.ts +50 -0
  40. package/template/apps/server/src/audit/drizzle-log-store.ts +25 -0
  41. package/template/apps/server/src/audit/search-summary.ts +2 -6
  42. package/template/apps/server/src/auth/credential-restriction.ts +1 -0
  43. package/template/apps/server/src/auth/password.ts +30 -21
  44. package/template/apps/server/src/auth/routes.test.ts +44 -3
  45. package/template/apps/server/src/auth/routes.ts +6 -1
  46. package/template/apps/server/src/auth/runtime.ts +4 -1
  47. package/template/apps/server/src/auth/service.ts +25 -7
  48. package/template/apps/server/src/configuration/bootstrap.ts +20 -1
  49. package/template/apps/server/src/configuration/definition.test.ts +71 -0
  50. package/template/apps/server/src/configuration/definition.ts +242 -12
  51. package/template/apps/server/src/configuration/drizzle-repository.ts +36 -2
  52. package/template/apps/server/src/configuration/repository.ts +27 -2
  53. package/template/apps/server/src/configuration/routes.ts +4 -1
  54. package/template/apps/server/src/configuration/service.test.ts +100 -0
  55. package/template/apps/server/src/configuration/service.ts +50 -10
  56. package/template/apps/server/src/data-access/scoped-repository.integration.test.ts +3 -2
  57. package/template/apps/server/src/data-governance/export/service.ts +2 -6
  58. package/template/apps/server/src/example-modules.ts +4 -1
  59. package/template/apps/server/src/guards.ts +18 -5
  60. package/template/apps/server/src/http/request-source.test.ts +85 -0
  61. package/template/apps/server/src/http/request-source.ts +90 -0
  62. package/template/apps/server/src/index.ts +45 -45
  63. package/template/apps/server/src/license/routes.ts +2 -0
  64. package/template/apps/server/src/license/runtime-service.ts +10 -11
  65. package/template/apps/server/src/module-bootstrap.ts +2 -2
  66. package/template/apps/server/src/module-host/initialize-contracts.ts +6 -0
  67. package/template/apps/server/src/module-host/initialize.ts +30 -44
  68. package/template/apps/server/src/module-host/request-context.ts +29 -23
  69. package/template/apps/server/src/module-host/resource-file-policies.test.ts +197 -0
  70. package/template/apps/server/src/module-host/resource-file-policies.ts +108 -0
  71. package/template/apps/server/src/module-host/route-declaration.ts +45 -0
  72. package/template/apps/server/src/module-host/route-installer.ts +27 -3
  73. package/template/apps/server/src/module-host/storage-port.test.ts +170 -1
  74. package/template/apps/server/src/module-host/storage-port.ts +134 -1
  75. package/template/apps/server/src/module-host/storage-scope.ts +89 -0
  76. package/template/apps/server/src/module-host/tool-handlers.test.ts +305 -0
  77. package/template/apps/server/src/module-host/tool-handlers.ts +38 -4
  78. package/template/apps/server/src/module-host/user-directory-composition.test.ts +126 -0
  79. package/template/apps/server/src/module-host/user-directory-port.test.ts +238 -0
  80. package/template/apps/server/src/module-host/user-directory-port.ts +139 -0
  81. package/template/apps/server/src/module-host.ts +4 -0
  82. package/template/apps/server/src/notification/channel-adapter.ts +46 -0
  83. package/template/apps/server/src/notification/delivery-policy.test.ts +59 -0
  84. package/template/apps/server/src/notification/delivery-policy.ts +37 -0
  85. package/template/apps/server/src/notification/delivery-repository.ts +198 -0
  86. package/template/apps/server/src/notification/delivery-worker.test.ts +87 -0
  87. package/template/apps/server/src/notification/delivery-worker.ts +88 -0
  88. package/template/apps/server/src/notification/drizzle-management.ts +180 -0
  89. package/template/apps/server/src/notification/drizzle-query.ts +160 -0
  90. package/template/apps/server/src/notification/drizzle-repository.integration.test.ts +301 -0
  91. package/template/apps/server/src/notification/drizzle-repository.ts +156 -90
  92. package/template/apps/server/src/notification/extension-routes.test.ts +119 -0
  93. package/template/apps/server/src/notification/extension-routes.ts +111 -0
  94. package/template/apps/server/src/notification/memory-delivery-repository.ts +137 -0
  95. package/template/apps/server/src/notification/memory-support.ts +72 -0
  96. package/template/apps/server/src/notification/preference-repository.ts +95 -0
  97. package/template/apps/server/src/notification/recipient-directory.ts +104 -0
  98. package/template/apps/server/src/notification/repository.test.ts +171 -0
  99. package/template/apps/server/src/notification/repository.ts +179 -32
  100. package/template/apps/server/src/notification/revision-routes.test.ts +186 -0
  101. package/template/apps/server/src/notification/route-support.ts +225 -0
  102. package/template/apps/server/src/notification/routes.test.ts +182 -0
  103. package/template/apps/server/src/notification/routes.ts +225 -132
  104. package/template/apps/server/src/notification/service.test.ts +104 -0
  105. package/template/apps/server/src/notification/service.ts +140 -0
  106. package/template/apps/server/src/notification/smtp-adapter.test.ts +65 -0
  107. package/template/apps/server/src/notification/smtp-adapter.ts +68 -0
  108. package/template/apps/server/src/persistence.integration.test.ts +41 -2
  109. package/template/apps/server/src/route-guards.ts +1 -0
  110. package/template/apps/server/src/runtime-audit-list.test.ts +39 -0
  111. package/template/apps/server/src/runtime-development.ts +2 -0
  112. package/template/apps/server/src/runtime.test.ts +23 -9
  113. package/template/apps/server/src/runtime.ts +39 -2
  114. package/template/apps/server/src/scheduler/audit.ts +2 -11
  115. package/template/apps/server/src/scheduler/definitions.test.ts +6 -0
  116. package/template/apps/server/src/scheduler/definitions.ts +7 -0
  117. package/template/apps/server/src/scheduler/recovery-service.ts +2 -11
  118. package/template/apps/server/src/scheduler/routes.ts +2 -7
  119. package/template/apps/server/src/search/opensearch-client.test.ts +45 -0
  120. package/template/apps/server/src/search/opensearch-client.ts +145 -0
  121. package/template/apps/server/src/search/service.ts +2 -6
  122. package/template/apps/server/src/settings/routes.ts +15 -3
  123. package/template/apps/server/src/settings/runtime.ts +10 -1
  124. package/template/apps/server/src/storage/runtime.ts +13 -0
  125. package/template/apps/server/src/system/audit-query.ts +33 -4
  126. package/template/apps/server/src/system/audit-routes.test.ts +58 -4
  127. package/template/apps/server/src/system/audit-routes.ts +6 -5
  128. package/template/apps/server/src/system/built-in-roles.ts +1 -0
  129. package/template/apps/server/src/system/department-organization.ts +320 -0
  130. package/template/apps/server/src/system/department-routes.test.ts +207 -48
  131. package/template/apps/server/src/system/department-routes.ts +114 -33
  132. package/template/apps/server/src/system/drizzle-repository.ts +15 -7
  133. package/template/apps/server/src/system/drizzle-scoped-repository.ts +5 -3
  134. package/template/apps/server/src/system/drizzle-system.ts +37 -9
  135. package/template/apps/server/src/system/drizzle-transaction-context.ts +14 -0
  136. package/template/apps/server/src/system/identity-route-config.ts +7 -2
  137. package/template/apps/server/src/system/mutation-runner.ts +23 -0
  138. package/template/apps/server/src/system/organization-postgres.integration.test.ts +176 -0
  139. package/template/apps/server/src/system/resource-access-response.ts +12 -6
  140. package/template/apps/server/src/system/role-department-scope-policy.ts +55 -0
  141. package/template/apps/server/src/system/route-helpers.ts +2 -5
  142. package/template/apps/server/src/system/route-types.ts +12 -3
  143. package/template/apps/server/src/system/routes-validation.test.ts +26 -0
  144. package/template/apps/server/src/system/routes.test.ts +10 -2
  145. package/template/apps/server/src/system/routes.ts +70 -36
  146. package/template/apps/server/src/system/seed.ts +7 -0
  147. package/template/apps/server/src/work-order/data-scope.integration.test.ts +9 -2
  148. package/template/apps/server/src/work-order/opensearch-index.test.ts +97 -0
  149. package/template/apps/server/src/work-order/opensearch-index.ts +313 -0
  150. package/template/apps/server/src/work-order/search-provider.ts +60 -1
  151. package/template/apps/server/src/work-order/service.ts +42 -10
  152. package/template/apps/web/src/app/error-pages.ts +9 -0
  153. package/template/apps/web/src/components/auth/PasswordChangeForm.vue +16 -11
  154. package/template/apps/web/src/components/auth/PasswordChangeForm.webtest.ts +14 -0
  155. package/template/apps/web/src/components/common/FormDialog.vue +16 -2
  156. package/template/apps/web/src/components/common/ModalLayout.webtest.ts +20 -1
  157. package/template/apps/web/src/composables/useGlobalSearch.ts +68 -0
  158. package/template/apps/web/src/composables/usePlatformSettings.ts +8 -7
  159. package/template/apps/web/src/layout/GlobalSearchResults.vue +58 -0
  160. package/template/apps/web/src/layout/NavigationSearchDialog.vue +42 -6
  161. package/template/apps/web/src/layout/NavigationSearchDialog.webtest.ts +71 -0
  162. package/template/apps/web/src/layout/NotificationMenu.vue +73 -31
  163. package/template/apps/web/src/layout/NotificationMenu.webtest.ts +44 -1
  164. package/template/apps/web/src/layout/NotificationMenuItem.vue +61 -0
  165. package/template/apps/web/src/layout/NotificationPreferencesDialog.vue +181 -0
  166. package/template/apps/web/src/layout/NotificationPreferencesDialog.webtest.ts +80 -0
  167. package/template/apps/web/src/lib/date-time.ts +22 -10
  168. package/template/apps/web/src/lib/date-time.webtest.ts +9 -1
  169. package/template/apps/web/src/main.ts +14 -1
  170. package/template/apps/web/src/pages/auth/ChangePasswordPage.vue +16 -1
  171. package/template/apps/web/src/pages/configuration/components/ModuleConfigVersionList.vue +1 -0
  172. package/template/apps/web/src/pages/configuration/components/ModuleConfigurationEditor.vue +10 -0
  173. package/template/apps/web/src/pages/configuration/components/ModuleConfigurationWorkbench.vue +37 -6
  174. package/template/apps/web/src/pages/errors/PlatformErrorPage.vue +115 -0
  175. package/template/apps/web/src/pages/errors/PlatformErrorPage.webtest.ts +44 -0
  176. package/template/apps/web/src/pages/errors/error-page-extension.ts +26 -0
  177. package/template/apps/web/src/pages/system/SystemNotificationsPage.vue +136 -68
  178. package/template/apps/web/src/pages/system/SystemNotificationsPage.webtest.ts +224 -12
  179. package/template/apps/web/src/pages/system/audit-log-presenter.ts +86 -0
  180. package/template/apps/web/src/pages/system/audit-time-range.ts +28 -0
  181. package/template/apps/web/src/pages/system/components/AuditActionCell.vue +10 -69
  182. package/template/apps/web/src/pages/system/components/AuditActionCell.webtest.ts +20 -27
  183. package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.vue +134 -0
  184. package/template/apps/web/src/pages/system/components/AuditLogDetailDialog.webtest.ts +76 -0
  185. package/template/apps/web/src/pages/system/components/AuditLogFilters.vue +116 -52
  186. package/template/apps/web/src/pages/system/components/AuditLogFilters.webtest.ts +52 -15
  187. package/template/apps/web/src/pages/system/components/NotificationHistoryDialog.vue +129 -0
  188. package/template/apps/web/src/pages/system/components/NotificationManagementCard.vue +91 -0
  189. package/template/apps/web/src/pages/system/components/NotificationPublishDialog.vue +121 -0
  190. package/template/apps/web/src/pages/system/components/NotificationPublishForm.vue +162 -1
  191. package/template/apps/web/src/pages/system/components/SystemResourceActions.vue +68 -0
  192. package/template/apps/web/src/pages/system/components/SystemResourceEditor.vue +74 -0
  193. package/template/apps/web/src/pages/system/components/SystemResourceFilters.vue +3 -4
  194. package/template/apps/web/src/pages/system/components/SystemResourcePage.vue +91 -98
  195. package/template/apps/web/src/pages/system/components/SystemResourcePage.webtest.ts +68 -0
  196. package/template/apps/web/src/pages/system/components/SystemResourceTable.vue +20 -1
  197. package/template/apps/web/src/pages/system/components/SystemResourceTable.webtest.ts +28 -7
  198. package/template/apps/web/src/pages/system/components/department/DepartmentOrganizationForm.vue +210 -0
  199. package/template/apps/web/src/pages/system/components/department/DepartmentTransitionDialog.vue +112 -0
  200. package/template/apps/web/src/pages/system/components/user-account/UserAccountForm.vue +17 -4
  201. package/template/apps/web/src/pages/system/composables/useDepartmentTransitions.ts +79 -0
  202. package/template/apps/web/src/pages/system/composables/useNotificationPermissions.ts +14 -0
  203. package/template/apps/web/src/pages/system/composables/useSystemResource.ts +2 -0
  204. package/template/apps/web/src/pages/system/composables/useSystemResource.webtest.ts +15 -2
  205. package/template/apps/web/src/pages/system/resource-config.ts +12 -4
  206. package/template/apps/web/src/router/error-navigation.ts +50 -0
  207. package/template/apps/web/src/router/error-navigation.webtest.ts +106 -0
  208. package/template/apps/web/src/router/index.ts +71 -0
  209. package/template/apps/web/src/router/index.webtest.ts +29 -0
  210. package/template/apps/web/src/router/manifest-routes.webtest.ts +4 -0
  211. package/template/apps/web/src/router/shared-scaffold-neutrality.webtest.ts +12 -0
  212. package/template/apps/web/src/services/auth-api.ts +5 -0
  213. package/template/apps/web/src/services/http.ts +43 -6
  214. package/template/apps/web/src/services/http.webtest.ts +70 -0
  215. package/template/apps/web/src/services/notification-api.ts +85 -2
  216. package/template/apps/web/src/services/notification-api.webtest.ts +110 -0
  217. package/template/apps/web/src/services/search-api.ts +39 -0
  218. package/template/apps/web/src/services/system-api.ts +18 -0
  219. package/template/apps/web/vitest.config.ts +1 -0
  220. package/template/docker-compose.yml +68 -0
  221. package/template/docs/architecture/ai-egress.md +49 -7
  222. package/template/docs/architecture/ai-runtime.md +34 -6
  223. package/template/docs/architecture/data-scope-query-enforcement.md +4 -0
  224. package/template/docs/architecture/object-storage.md +22 -2
  225. package/template/docs/architecture/search-provider-protocol.md +38 -2
  226. package/template/docs/development/custom-error-pages.md +78 -0
  227. package/template/docs/development/custom-login-page.md +3 -3
  228. package/template/docs/platform/agent-runtime.md +76 -7
  229. package/template/docs/platform/audit-reliability.md +46 -2
  230. package/template/docs/platform/business-user-directory.md +82 -0
  231. package/template/docs/platform/module-configuration.md +13 -4
  232. package/template/docs/platform/notifications.md +67 -11
  233. package/template/docs/platform/organization-membership.md +17 -3
  234. package/template/docs/platform/system-crud-api.md +16 -0
  235. package/template/docs/platform/web-system-crud.md +6 -2
  236. package/template/package.json +4 -0
  237. package/template/packages/config/index.test.ts +45 -0
  238. package/template/packages/config/package.json +1 -1
  239. package/template/packages/config/src/platform.ts +169 -48
  240. package/template/packages/database/drizzle/0035_material_nightshade.sql +10 -0
  241. package/template/packages/database/drizzle/0036_hesitant_speed.sql +35 -0
  242. package/template/packages/database/drizzle/0037_absent_nick_fury.sql +2 -0
  243. package/template/packages/database/drizzle/0038_uneven_wasp.sql +38 -0
  244. package/template/packages/database/drizzle/meta/0035_snapshot.json +7331 -0
  245. package/template/packages/database/drizzle/meta/0036_snapshot.json +7436 -0
  246. package/template/packages/database/drizzle/meta/0037_snapshot.json +7457 -0
  247. package/template/packages/database/drizzle/meta/0038_snapshot.json +7749 -0
  248. package/template/packages/database/drizzle/meta/_journal.json +28 -0
  249. package/template/packages/database/package.json +1 -1
  250. package/template/packages/database/src/schema/identity.ts +7 -0
  251. package/template/packages/database/src/schema/notifications.ts +135 -0
  252. package/template/packages/database/src/schema-notification-delivery.test.ts +54 -0
  253. package/template/packages/database/src/schema-workflow-notification.test.ts +40 -1
  254. package/template/packages/modules/package.json +1 -1
  255. package/template/packages/modules/src/ai-operation-composition.test.ts +68 -0
  256. package/template/packages/modules/src/ai-operation-composition.ts +115 -0
  257. package/template/packages/modules/src/manifest.ts +26 -0
  258. package/template/packages/modules/src/provider-data-policy-composition.test.ts +74 -0
  259. package/template/packages/modules/src/system-api-permissions.ts +54 -0
  260. package/template/packages/modules/src/system-audit-actions.ts +15 -0
  261. package/template/packages/modules/src/system-features.ts +6 -1
  262. package/template/packages/modules/src/system-module-catalog.test.ts +16 -0
  263. package/template/packages/modules/src/system-module-catalog.ts +28 -4
  264. package/template/packages/modules/src/system-modules.test.ts +13 -1
  265. package/template/packages/modules/src/system-modules.ts +12 -0
  266. package/template/packages/modules/src/system-permissions.ts +11 -5
  267. package/template/packages/server-sdk/index.ts +11 -0
  268. package/template/packages/server-sdk/package.json +3 -2
  269. package/template/packages/server-sdk/src/ai-operation-registration.ts +2 -1
  270. package/template/packages/server-sdk/src/module-host.ts +8 -0
  271. package/template/packages/server-sdk/src/request-context.ts +2 -0
  272. package/template/packages/server-sdk/src/storage-port.ts +66 -0
  273. package/template/packages/server-sdk/src/tool-registration.ts +6 -0
  274. package/template/packages/server-sdk/src/user-directory-port.ts +42 -0
  275. package/template/packages/shared/package.json +1 -1
  276. package/template/packages/shared/src/audit.ts +25 -0
  277. package/template/packages/shared/src/module-configuration.ts +5 -0
  278. package/template/packages/shared/src/notification.ts +92 -1
  279. package/template/packages/shared/src/password.ts +7 -2
  280. package/template/packages/shared/src/platform-settings.ts +2 -0
  281. package/template/packages/storage/package.json +1 -1
  282. package/template/packages/storage/src/upload-service.test.ts +16 -0
  283. package/template/packages/storage/src/upload-service.ts +5 -1
  284. package/template/scripts/notification-postgres-regression.ts +27 -0
@@ -7,6 +7,7 @@ from typing import Protocol
7
7
  from .contracts import (
8
8
  AgentErrorCode,
9
9
  JsonValue,
10
+ ProviderDataPolicy,
10
11
  ProviderEgress,
11
12
  RuntimeLimits,
12
13
  RuntimeToolDefinition,
@@ -24,6 +25,7 @@ class EngineRunRequest:
24
25
  allowed_tools: tuple[RuntimeToolDefinition, ...] = ()
25
26
  limits: RuntimeLimits = field(default_factory=RuntimeLimits)
26
27
  provider_egress: ProviderEgress = field(default_factory=ProviderEgress)
28
+ provider_data_policy: ProviderDataPolicy = field(default_factory=ProviderDataPolicy)
27
29
  tool_network_policy: ToolNetworkPolicy = field(default_factory=ToolNetworkPolicy)
28
30
  execution_grant: str = ""
29
31
  tool_host_url: str = ""
@@ -17,6 +17,7 @@ from .engine import (
17
17
  UsageReported,
18
18
  )
19
19
  from .openai_provider import OpenAiCompatibleProvider, ProviderToolCall
20
+ from .provider_governance import ProviderRequestGovernance
20
21
  from .tool_host import ToolHostClient, ToolInvocation
21
22
 
22
23
 
@@ -25,9 +26,11 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
25
26
  self,
26
27
  provider: OpenAiCompatibleProvider,
27
28
  tool_host: ToolHostClient,
29
+ governance: ProviderRequestGovernance,
28
30
  ) -> None:
29
31
  self._provider = provider
30
32
  self._tool_host = tool_host
33
+ self._governance = governance
31
34
  self._cancellations: dict[str, asyncio.Event] = {}
32
35
 
33
36
  def supports(self, operation_key: str) -> bool:
@@ -49,11 +52,24 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
49
52
  ]
50
53
  output_bytes = 0
51
54
  try:
52
- for _round in range(self._provider.config.max_tool_rounds):
55
+ for attempt in range(1, self._provider.config.max_tool_rounds + 1):
53
56
  cancelled(cancellation)
57
+ governed = self._governance.authorize(
58
+ deployment_key=self._provider.config.deployment_key,
59
+ operation_key=request.operation_key,
60
+ run_id=request.run_id,
61
+ attempt=attempt,
62
+ messages=messages,
63
+ tools=request.allowed_tools,
64
+ policy=request.provider_data_policy,
65
+ input_value=request.input,
66
+ measure=lambda messages, tools: len(
67
+ self._provider.request_body(messages, tools)
68
+ ),
69
+ )
54
70
  turn = await self._provider.complete_turn(
55
- messages,
56
- request.allowed_tools,
71
+ governed.messages,
72
+ governed.tools,
57
73
  )
58
74
  if turn.tool_calls:
59
75
  messages.append(assistant_tool_message(turn.tool_calls))
@@ -135,6 +151,9 @@ class OpenAiCompatibleEngineAdapter(AgentEngineAdapter):
135
151
  async def health(self) -> None:
136
152
  await self._provider.health()
137
153
 
154
+ def governance_health(self) -> dict[str, int | float | str | None]:
155
+ return self._governance.health()
156
+
138
157
 
139
158
  def validate_runtime_request(
140
159
  request: EngineRunRequest,
@@ -166,6 +185,15 @@ def validate_runtime_request(
166
185
  "Agent Tool 网络策略不受支持",
167
186
  retryable=False,
168
187
  )
188
+ if (
189
+ not request.provider_data_policy.input_fields
190
+ or not request.provider_data_policy.envelope_fields
191
+ ):
192
+ raise SafeEngineError(
193
+ "FORBIDDEN",
194
+ "Agent Operation 缺少 Provider 字段治理策略",
195
+ retryable=False,
196
+ )
169
197
 
170
198
 
171
199
  def cancelled(event: asyncio.Event) -> None:
@@ -59,21 +59,15 @@ class OpenAiCompatibleProvider:
59
59
  messages: list[dict[str, JsonValue]],
60
60
  tools: tuple[RuntimeToolDefinition, ...],
61
61
  ) -> ProviderTurn:
62
- payload: dict[str, JsonValue] = {
63
- "model": self.config.model,
64
- "messages": cast(JsonValue, messages),
65
- "stream": True,
66
- "stream_options": {"include_usage": True},
67
- }
68
- if tools:
69
- payload["tools"] = [tool_schema(tool) for tool in tools]
62
+ body = self.request_body(messages, tools)
70
63
  turn = ProviderTurn()
71
64
  async with self._client() as client:
72
65
  try:
73
66
  async with client.stream(
74
67
  "POST",
75
68
  "/chat/completions",
76
- json=payload,
69
+ content=body,
70
+ headers={"content-type": "application/json"},
77
71
  ) as response:
78
72
  if not response.is_success:
79
73
  await self._bounded_stream(response)
@@ -85,6 +79,25 @@ class OpenAiCompatibleProvider:
85
79
  raise provider_failure(error) from None
86
80
  return turn
87
81
 
82
+ def request_body(
83
+ self,
84
+ messages: list[dict[str, JsonValue]],
85
+ tools: tuple[RuntimeToolDefinition, ...],
86
+ ) -> bytes:
87
+ payload: dict[str, JsonValue] = {
88
+ "model": self.config.model,
89
+ "messages": cast(JsonValue, messages),
90
+ "stream": True,
91
+ "stream_options": {"include_usage": True},
92
+ }
93
+ if tools:
94
+ payload["tools"] = [tool_schema(tool) for tool in tools]
95
+ return json.dumps(
96
+ payload,
97
+ ensure_ascii=False,
98
+ separators=(",", ":"),
99
+ ).encode()
100
+
88
101
  def _client(self) -> httpx.AsyncClient:
89
102
  api_key = (
90
103
  self._resolver.resolve(self.config.api_key_ref)
@@ -0,0 +1,144 @@
1
+ from __future__ import annotations
2
+
3
+ import sqlite3
4
+ import threading
5
+ import time
6
+ import uuid
7
+ from dataclasses import dataclass
8
+ from pathlib import Path
9
+
10
+
11
+ @dataclass(frozen=True)
12
+ class ProviderAttemptAudit:
13
+ deployment_key: str
14
+ operation_key: str
15
+ run_id: str
16
+ attempt: int
17
+ classifications: tuple[str, ...]
18
+ sent_bytes: int
19
+ decision: str
20
+
21
+
22
+ class ProviderAuditStore:
23
+ """Append-only Provider attempt metadata, except explicit TTL expiry."""
24
+
25
+ def __init__(self, path: str, *, retention_days: int) -> None:
26
+ if not path.strip():
27
+ raise ValueError("Provider 发送审计 Store 路径不能为空")
28
+ if not 1 <= retention_days <= 3650:
29
+ raise ValueError("Provider 发送审计留存天数超出范围")
30
+ Path(path).parent.mkdir(parents=True, exist_ok=True)
31
+ self._database = sqlite3.connect(path, check_same_thread=False)
32
+ self._lock = threading.RLock()
33
+ self._retention_seconds = retention_days * 24 * 60 * 60
34
+ self.retention_days = retention_days
35
+ self._initialize()
36
+ self.purge_expired()
37
+
38
+ def append(self, audit: ProviderAttemptAudit) -> None:
39
+ with self._lock:
40
+ self.purge_expired()
41
+ try:
42
+ self._database.execute(
43
+ """
44
+ insert into provider_attempt_audit (
45
+ event_id, deployment_key, operation_key, run_id, attempt,
46
+ classifications, sent_bytes, decision, occurred_at
47
+ ) values (?, ?, ?, ?, ?, ?, ?, ?, ?)
48
+ """,
49
+ (
50
+ f"pa_{uuid.uuid4().hex}",
51
+ audit.deployment_key,
52
+ audit.operation_key,
53
+ audit.run_id,
54
+ audit.attempt,
55
+ ",".join(audit.classifications),
56
+ audit.sent_bytes,
57
+ audit.decision,
58
+ time.time(),
59
+ ),
60
+ )
61
+ self._database.commit()
62
+ except Exception:
63
+ self._database.rollback()
64
+ raise
65
+
66
+ def purge_expired(self, *, now: float | None = None) -> int:
67
+ with self._lock:
68
+ cutoff = (now or time.time()) - self._retention_seconds
69
+ cursor = self._database.execute(
70
+ "delete from provider_attempt_audit where occurred_at < ?",
71
+ (cutoff,),
72
+ )
73
+ deleted = max(cursor.rowcount, 0)
74
+ self._database.execute(
75
+ """
76
+ update provider_audit_retention
77
+ set last_purge_at = ?, last_deleted = ?,
78
+ total_deleted = total_deleted + ?
79
+ where singleton = 1
80
+ """,
81
+ (now or time.time(), deleted, deleted),
82
+ )
83
+ self._database.commit()
84
+ return deleted
85
+
86
+ def health(self) -> dict[str, int | float | str | None]:
87
+ with self._lock:
88
+ return self._health()
89
+
90
+ def _health(self) -> dict[str, int | float | str | None]:
91
+ row = self._database.execute(
92
+ """
93
+ select count(*) as retained,
94
+ sum(case when decision = 'reject' then 1 else 0 end) as rejected,
95
+ sum(case when decision = 'redact' then 1 else 0 end) as redacted,
96
+ max(occurred_at) as last_attempt_at
97
+ from provider_attempt_audit
98
+ """
99
+ ).fetchone()
100
+ retention = self._database.execute(
101
+ "select * from provider_audit_retention where singleton = 1"
102
+ ).fetchone()
103
+ return {
104
+ "status": "healthy",
105
+ "retentionDays": self.retention_days,
106
+ "retainedRecords": int(row[0] or 0),
107
+ "rejectedAttempts": int(row[1] or 0),
108
+ "redactedAttempts": int(row[2] or 0),
109
+ "lastAttemptAt": row[3],
110
+ "lastPurgeAt": retention[1],
111
+ "lastDeletedRecords": int(retention[2]),
112
+ "totalDeletedRecords": int(retention[3]),
113
+ }
114
+
115
+ def close(self) -> None:
116
+ with self._lock:
117
+ self._database.close()
118
+
119
+ def _initialize(self) -> None:
120
+ self._database.executescript(
121
+ """
122
+ create table if not exists provider_attempt_audit (
123
+ event_id text primary key,
124
+ deployment_key text not null,
125
+ operation_key text not null,
126
+ run_id text not null,
127
+ attempt integer not null,
128
+ classifications text not null,
129
+ sent_bytes integer not null,
130
+ decision text not null check(decision in ('allow', 'redact', 'reject')),
131
+ occurred_at real not null,
132
+ unique(run_id, attempt)
133
+ );
134
+ create table if not exists provider_audit_retention (
135
+ singleton integer primary key check(singleton = 1),
136
+ last_purge_at real,
137
+ last_deleted integer not null,
138
+ total_deleted integer not null
139
+ );
140
+ insert or ignore into provider_audit_retention
141
+ values (1, null, 0, 0);
142
+ """
143
+ )
144
+ self._database.commit()
@@ -0,0 +1,227 @@
1
+ from __future__ import annotations
2
+
3
+ import json
4
+ import re
5
+ from collections.abc import Callable
6
+ from dataclasses import dataclass
7
+ from typing import cast
8
+
9
+ from .contracts import (
10
+ JsonValue,
11
+ ProviderDataField,
12
+ ProviderDataPolicy,
13
+ RuntimeToolDefinition,
14
+ )
15
+ from .engine import SafeEngineError
16
+ from .provider_audit import ProviderAttemptAudit, ProviderAuditStore
17
+
18
+ _DLP_PATTERNS = (
19
+ re.compile(r"(?i)\bBearer\s+[A-Za-z0-9._~+/=-]{8,}"),
20
+ re.compile(r"(?i)\b(?:api[_-]?key|secret|password|token)\s*[:=]\s*\S+"),
21
+ re.compile(r"\b1[3-9]\d{9}\b"),
22
+ re.compile(r"\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b", re.I),
23
+ re.compile(r"\b\d{17}[\dXx]\b"),
24
+ re.compile(r"\b(?:\d[ -]?){16,30}\b"),
25
+ )
26
+ _REDACTED = "[REDACTED]"
27
+
28
+
29
+ @dataclass(frozen=True)
30
+ class GovernedAttempt:
31
+ messages: list[dict[str, JsonValue]]
32
+ tools: tuple[RuntimeToolDefinition, ...]
33
+
34
+
35
+ class ProviderRequestGovernance:
36
+ def __init__(self, audit: ProviderAuditStore) -> None:
37
+ self.audit = audit
38
+
39
+ def authorize(
40
+ self,
41
+ *,
42
+ deployment_key: str,
43
+ operation_key: str,
44
+ run_id: str,
45
+ attempt: int,
46
+ messages: list[dict[str, JsonValue]],
47
+ tools: tuple[RuntimeToolDefinition, ...],
48
+ policy: ProviderDataPolicy,
49
+ input_value: JsonValue = None,
50
+ measure: Callable[
51
+ [list[dict[str, JsonValue]], tuple[RuntimeToolDefinition, ...]], int
52
+ ]
53
+ | None = None,
54
+ ) -> GovernedAttempt:
55
+ rules = {rule.field: rule for rule in policy.envelope_fields}
56
+ classified: set[str] = set()
57
+ classified.update(input_classifications(input_value, policy))
58
+ redacted = False
59
+ governed_messages: list[dict[str, JsonValue]] = []
60
+ try:
61
+ for message in messages:
62
+ field = field_for_message(message)
63
+ rule = rules.get(field)
64
+ if rule is None:
65
+ raise GovernanceDenied(f"Provider 未授权发送字段:{field}")
66
+ classified.add(rule.classification)
67
+ value, changed = govern_value(message, rule.dlp_action)
68
+ redacted = redacted or changed
69
+ governed_messages.append(cast(dict[str, JsonValue], value))
70
+ governed_tools = tools
71
+ if tools:
72
+ rule = rules.get("tool-definitions")
73
+ if rule is None:
74
+ raise GovernanceDenied("Provider 未授权发送字段:tool-definitions")
75
+ classified.add(rule.classification)
76
+ raw_tools = [tool.model_dump(by_alias=True) for tool in tools]
77
+ governed, changed = govern_value(raw_tools, rule.dlp_action)
78
+ redacted = redacted or changed
79
+ governed_tools = tuple(
80
+ RuntimeToolDefinition.model_validate(item)
81
+ for item in cast(list[object], governed)
82
+ )
83
+ sent_bytes = (measure or payload_size)(
84
+ governed_messages,
85
+ governed_tools,
86
+ )
87
+ decision = "redact" if redacted else "allow"
88
+ self._record(
89
+ deployment_key,
90
+ operation_key,
91
+ run_id,
92
+ attempt,
93
+ classified,
94
+ sent_bytes,
95
+ decision,
96
+ )
97
+ return GovernedAttempt(governed_messages, governed_tools)
98
+ except GovernanceDenied:
99
+ self._record(
100
+ deployment_key,
101
+ operation_key,
102
+ run_id,
103
+ attempt,
104
+ classified,
105
+ 0,
106
+ "reject",
107
+ )
108
+ raise SafeEngineError(
109
+ "FORBIDDEN",
110
+ "Provider 请求被字段治理策略拒绝",
111
+ retryable=False,
112
+ ) from None
113
+
114
+ def health(self) -> dict[str, int | float | str | None]:
115
+ return self.audit.health()
116
+
117
+ def _record(
118
+ self,
119
+ deployment_key: str,
120
+ operation_key: str,
121
+ run_id: str,
122
+ attempt: int,
123
+ classified: set[str],
124
+ sent_bytes: int,
125
+ decision: str,
126
+ ) -> None:
127
+ try:
128
+ self.audit.append(
129
+ ProviderAttemptAudit(
130
+ deployment_key,
131
+ operation_key,
132
+ run_id,
133
+ attempt,
134
+ tuple(sorted(classified)),
135
+ sent_bytes,
136
+ decision,
137
+ )
138
+ )
139
+ except Exception:
140
+ raise SafeEngineError(
141
+ "FORBIDDEN",
142
+ "Provider 发送审计不可用",
143
+ retryable=False,
144
+ ) from None
145
+
146
+
147
+ class GovernanceDenied(Exception):
148
+ pass
149
+
150
+
151
+ def field_for_message(message: dict[str, JsonValue]) -> ProviderDataField:
152
+ role = message.get("role")
153
+ if role == "system":
154
+ return "instructions"
155
+ if role == "user":
156
+ return "user-prompt"
157
+ if role == "assistant":
158
+ return "provider-output"
159
+ if role == "tool":
160
+ return "tool-results"
161
+ raise GovernanceDenied("Provider 消息角色未知")
162
+
163
+
164
+ def input_classifications(
165
+ value: JsonValue,
166
+ policy: ProviderDataPolicy,
167
+ ) -> set[str]:
168
+ result: set[str] = set()
169
+ for rule in policy.input_fields:
170
+ current = value
171
+ found = True
172
+ for encoded in rule.json_pointer.removeprefix("/").split("/"):
173
+ segment = encoded.replace("~1", "/").replace("~0", "~")
174
+ if not isinstance(current, dict) or segment not in current:
175
+ found = False
176
+ break
177
+ current = current[segment]
178
+ if found:
179
+ result.add(rule.classification)
180
+ return result
181
+
182
+
183
+ def govern_value(value: object, action: str) -> tuple[object, bool]:
184
+ if isinstance(value, str):
185
+ detected = any(pattern.search(value) for pattern in _DLP_PATTERNS)
186
+ if detected and action == "reject":
187
+ raise GovernanceDenied("DLP reject")
188
+ if detected:
189
+ result = value
190
+ for pattern in _DLP_PATTERNS:
191
+ result = pattern.sub(_REDACTED, result)
192
+ return result, result != value
193
+ return value, False
194
+ if isinstance(value, list):
195
+ changed = False
196
+ items: list[object] = []
197
+ for item in value:
198
+ governed, item_changed = govern_value(item, action)
199
+ items.append(governed)
200
+ changed = changed or item_changed
201
+ return items, changed
202
+ if isinstance(value, dict):
203
+ changed = False
204
+ governed_record: dict[str, object] = {}
205
+ for key, item in value.items():
206
+ governed, item_changed = govern_value(item, action)
207
+ governed_record[key] = governed
208
+ changed = changed or item_changed
209
+ return governed_record, changed
210
+ return value, False
211
+
212
+
213
+ def payload_size(
214
+ messages: list[dict[str, JsonValue]],
215
+ tools: tuple[RuntimeToolDefinition, ...],
216
+ ) -> int:
217
+ payload = {
218
+ "messages": messages,
219
+ "tools": [tool.model_dump(by_alias=True) for tool in tools],
220
+ }
221
+ return len(
222
+ json.dumps(
223
+ payload,
224
+ ensure_ascii=False,
225
+ separators=(",", ":"),
226
+ ).encode()
227
+ )
@@ -86,6 +86,7 @@ class AgentRunService:
86
86
  allowed_tools=tuple(invocation.allowed_tools),
87
87
  limits=invocation.limits,
88
88
  provider_egress=invocation.provider_egress,
89
+ provider_data_policy=invocation.provider_data_policy,
89
90
  tool_network_policy=invocation.tool_network_policy,
90
91
  execution_grant=invocation.execution_grant,
91
92
  tool_host_url=invocation.tool_host_url,