create-windy 0.6.14 → 0.6.16

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 (162) hide show
  1. package/dist/cli.js +1008 -61
  2. package/package.json +1 -1
  3. package/template/.agents/skills/windy-business-module/SKILL.md +58 -8
  4. package/template/.windy-template.json +2 -2
  5. package/template/apps/server/src/agent/client-session/attempt-authority.test.ts +162 -0
  6. package/template/apps/server/src/agent/client-session/attempt-authority.ts +191 -0
  7. package/template/apps/server/src/agent/client-session/bootstrap.ts +27 -11
  8. package/template/apps/server/src/agent/client-session/completion.ts +75 -24
  9. package/template/apps/server/src/agent/client-session/contracts.ts +23 -0
  10. package/template/apps/server/src/agent/client-session/creation.ts +103 -0
  11. package/template/apps/server/src/agent/client-session/digest.ts +11 -1
  12. package/template/apps/server/src/agent/client-session/drizzle-records.ts +150 -0
  13. package/template/apps/server/src/agent/client-session/drizzle-repository.integration.test.ts +30 -3
  14. package/template/apps/server/src/agent/client-session/drizzle-repository.ts +23 -132
  15. package/template/apps/server/src/agent/client-session/finalization.ts +99 -0
  16. package/template/apps/server/src/agent/client-session/host-auth.ts +22 -10
  17. package/template/apps/server/src/agent/client-session/host-revocation-composition.test.ts +77 -0
  18. package/template/apps/server/src/agent/client-session/host-revocation-composition.ts +28 -0
  19. package/template/apps/server/src/agent/client-session/launch-authority.contract.test.ts +21 -0
  20. package/template/apps/server/src/agent/client-session/manifest-admission.ts +11 -0
  21. package/template/apps/server/src/agent/client-session/service.test-support.ts +22 -0
  22. package/template/apps/server/src/agent/client-session/service.ts +104 -134
  23. package/template/apps/server/src/agent/client-session/terminal-digest.test.ts +23 -0
  24. package/template/apps/server/src/agent/client-workflow-artifact/active-blob-references.ts +8 -0
  25. package/template/apps/server/src/agent/client-workflow-artifact/artifact-builder.ts +60 -0
  26. package/template/apps/server/src/agent/client-workflow-artifact/authority-current.ts +59 -0
  27. package/template/apps/server/src/agent/client-workflow-artifact/binding-registry.test.ts +189 -0
  28. package/template/apps/server/src/agent/client-workflow-artifact/binding-registry.ts +153 -0
  29. package/template/apps/server/src/agent/client-workflow-artifact/bootstrap.ts +77 -0
  30. package/template/apps/server/src/agent/client-workflow-artifact/cancellation.ts +47 -0
  31. package/template/apps/server/src/agent/client-workflow-artifact/completed-recovery.ts +31 -0
  32. package/template/apps/server/src/agent/client-workflow-artifact/contracts.ts +104 -0
  33. package/template/apps/server/src/agent/client-workflow-artifact/drizzle-locks.ts +30 -0
  34. package/template/apps/server/src/agent/client-workflow-artifact/drizzle-mapper.ts +138 -0
  35. package/template/apps/server/src/agent/client-workflow-artifact/drizzle-repository.ts +346 -0
  36. package/template/apps/server/src/agent/client-workflow-artifact/error.ts +21 -0
  37. package/template/apps/server/src/agent/client-workflow-artifact/handler.ts +167 -0
  38. package/template/apps/server/src/agent/client-workflow-artifact/host-revocation.ts +29 -0
  39. package/template/apps/server/src/agent/client-workflow-artifact/in-memory-repository.ts +232 -0
  40. package/template/apps/server/src/agent/client-workflow-artifact/job-port.ts +101 -0
  41. package/template/apps/server/src/agent/client-workflow-artifact/presentation.ts +41 -0
  42. package/template/apps/server/src/agent/client-workflow-artifact/recovery.contract.test.ts +253 -0
  43. package/template/apps/server/src/agent/client-workflow-artifact/release-binding.ts +85 -0
  44. package/template/apps/server/src/agent/client-workflow-artifact/repository.test.ts +204 -0
  45. package/template/apps/server/src/agent/client-workflow-artifact/repository.ts +97 -0
  46. package/template/apps/server/src/agent/client-workflow-artifact/routes-handler.contract.test.ts +201 -0
  47. package/template/apps/server/src/agent/client-workflow-artifact/routes.ts +161 -0
  48. package/template/apps/server/src/agent/client-workflow-artifact/runtime-composition.ts +101 -0
  49. package/template/apps/server/src/agent/client-workflow-artifact/scope-authority.ts +39 -0
  50. package/template/apps/server/src/agent/client-workflow-artifact/service-authorization.ts +117 -0
  51. package/template/apps/server/src/agent/client-workflow-artifact/service-support.ts +28 -0
  52. package/template/apps/server/src/agent/client-workflow-artifact/service.contract.test.ts +345 -0
  53. package/template/apps/server/src/agent/client-workflow-artifact/service.ts +350 -0
  54. package/template/apps/server/src/agent/client-workflow-artifact/session-boundary.ts +39 -0
  55. package/template/apps/server/src/agent/client-workflow-artifact/source-access.contract.test.ts +98 -0
  56. package/template/apps/server/src/agent/client-workflow-artifact/source-access.ts +80 -0
  57. package/template/apps/server/src/agent/client-workflow-artifact/takeover.ts +122 -0
  58. package/template/apps/server/src/agent/client-workflow-artifact/task-authority.contract.test.ts +97 -0
  59. package/template/apps/server/src/agent/client-workflow-artifact/task-authority.ts +147 -0
  60. package/template/apps/server/src/agent/client-workflow-artifact/terminal-authority.contract.test.ts +100 -0
  61. package/template/apps/server/src/agent/client-workflow-artifact/terminal-replay.ts +31 -0
  62. package/template/apps/server/src/agent/client-workflow-artifact/terminal-seal.ts +211 -0
  63. package/template/apps/server/src/agent/client-workflow-artifact/workflow-artifact.test-support.ts +173 -0
  64. package/template/apps/server/src/agent/client-workflow-artifact/workflow-loader.contract.test.ts +254 -0
  65. package/template/apps/server/src/agent/client-workflow-artifact/workflow-loader.ts +285 -0
  66. package/template/apps/server/src/agent/run-facade.test-support.ts +17 -4
  67. package/template/apps/server/src/agent/run-facade.test.ts +16 -0
  68. package/template/apps/server/src/agent/run-session-authorization.ts +12 -10
  69. package/template/apps/server/src/agent/runtime-bootstrap-input.ts +5 -1
  70. package/template/apps/server/src/agent/runtime-bootstrap.ts +33 -33
  71. package/template/apps/server/src/agent/runtime-public-ports.ts +40 -0
  72. package/template/apps/server/src/agent/session-invalidation/contracts.ts +25 -26
  73. package/template/apps/server/src/agent/session-projection-access.ts +31 -19
  74. package/template/apps/server/src/agent/unavailable-routes.ts +21 -0
  75. package/template/apps/server/src/agent/workflow-runtime-bootstrap.ts +2 -0
  76. package/template/apps/server/src/agent-workflow/artifact-source-authorization.ts +12 -6
  77. package/template/apps/server/src/agent-workflow/command-facade.ts +70 -11
  78. package/template/apps/server/src/agent-workflow/drizzle-artifact-port.ts +9 -9
  79. package/template/apps/server/src/agent-workflow/drizzle-delegation-completion.ts +1 -1
  80. package/template/apps/server/src/agent-workflow/platform-node-adapters.ts +14 -6
  81. package/template/apps/server/src/agent-workflow/project-launch-authority.contract.test.ts +53 -0
  82. package/template/apps/server/src/agent-workflow/project-launch-authority.ts +92 -0
  83. package/template/apps/server/src/agent-workflow/runtime-composition.ts +30 -14
  84. package/template/apps/server/src/app/agent-runtime-composition.ts +6 -1
  85. package/template/apps/server/src/app/client-agent-workflow-artifact-binding.ts +14 -0
  86. package/template/apps/server/src/application-services.ts +6 -0
  87. package/template/apps/server/src/data-access/scope-containment.test.ts +31 -0
  88. package/template/apps/server/src/data-access/scope-containment.ts +48 -0
  89. package/template/apps/server/src/jobs/artifact-publication.test.ts +192 -0
  90. package/template/apps/server/src/jobs/artifact-publication.ts +56 -0
  91. package/template/apps/server/src/jobs/artifact-publisher.ts +101 -0
  92. package/template/apps/server/src/jobs/drizzle-artifact.ts +73 -1
  93. package/template/apps/server/src/jobs/drizzle-execution-repository.ts +14 -1
  94. package/template/apps/server/src/jobs/drizzle-execution.integration.test.ts +65 -0
  95. package/template/apps/server/src/jobs/drizzle-repository.integration.test.ts +1 -0
  96. package/template/apps/server/src/jobs/execution-repository.ts +28 -0
  97. package/template/apps/server/src/jobs/execution.test.ts +3 -1
  98. package/template/apps/server/src/jobs/in-memory-execution.ts +16 -0
  99. package/template/apps/server/src/jobs/in-memory-lease.ts +45 -1
  100. package/template/apps/server/src/jobs/legacy-adapter.integration.test.ts +2 -0
  101. package/template/apps/server/src/jobs/postgres-cleanup-contract.test.ts +58 -0
  102. package/template/apps/server/src/jobs/worker.ts +8 -14
  103. package/template/apps/server/src/module-host/background-ai-unavailable.ts +18 -0
  104. package/template/apps/server/src/module-host/durable-context.ts +19 -22
  105. package/template/apps/server/src/module-host/implementation-completeness.test.ts +80 -1
  106. package/template/apps/server/src/module-host/implementation-completeness.ts +28 -1
  107. package/template/apps/server/src/module-host/request-context.ts +32 -8
  108. package/template/apps/server/src/module-host/storage-port.ts +3 -0
  109. package/template/apps/server/src/module-host/workflow-input-artifact-port.test.ts +104 -0
  110. package/template/apps/server/src/module-host/workflow-input-artifact-port.ts +229 -0
  111. package/template/apps/server/src/module-host/workflow-input-materialization.test.ts +163 -0
  112. package/template/apps/server/src/module-host/workflow-input-materialization.ts +161 -0
  113. package/template/apps/server/src/workflow/drizzle-repository.integration.test.ts +1 -0
  114. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.vue +2 -1
  115. package/template/apps/web/src/pages/system/components/ai-runtime/AiRuntimeFilters.webtest.ts +16 -0
  116. package/template/docs/architecture/agent-workflow.md +13 -2
  117. package/template/docs/architecture/durable-jobs.md +7 -0
  118. package/template/docs/operations/agent-session-memory.md +2 -1
  119. package/template/docs/operations/agent-session-troubleshooting.md +87 -2
  120. package/template/docs/operations/agent-workflow-recovery.md +25 -14
  121. package/template/docs/platform/client-agent-session.md +148 -2
  122. package/template/packages/ai-client/package.json +2 -1
  123. package/template/packages/ai-client/src/client-agent/index.ts +1 -0
  124. package/template/packages/ai-client/src/client-agent/workflow-artifact/contracts.ts +85 -0
  125. package/template/packages/ai-client/src/client-agent/workflow-artifact/http.test.ts +49 -0
  126. package/template/packages/ai-client/src/client-agent/workflow-artifact/http.ts +82 -0
  127. package/template/packages/ai-client/src/client-agent/workflow-artifact/index.ts +3 -0
  128. package/template/packages/ai-client/src/client-agent/workflow-artifact/validation.test.ts +154 -0
  129. package/template/packages/ai-client/src/client-agent/workflow-artifact/validation.ts +332 -0
  130. package/template/packages/database/drizzle/0062_client_agent_workflow_artifact.sql +63 -0
  131. package/template/packages/database/drizzle/meta/0062_snapshot.json +14839 -0
  132. package/template/packages/database/drizzle/meta/_journal.json +7 -0
  133. package/template/packages/database/package.json +1 -1
  134. package/template/packages/database/src/agent-runtime-schema.test.ts +177 -0
  135. package/template/packages/database/src/schema/client-agent-workflow-artifact.ts +158 -0
  136. package/template/packages/database/src/schema/index.ts +1 -0
  137. package/template/packages/jobs/package.json +1 -1
  138. package/template/packages/jobs/src/handlers.test.ts +10 -0
  139. package/template/packages/jobs/src/handlers.ts +28 -11
  140. package/template/packages/modules/package.json +1 -1
  141. package/template/packages/modules/src/ai-client-execution-policy.ts +6 -0
  142. package/template/packages/modules/src/manifest.ts +2 -0
  143. package/template/packages/modules/src/system-agent-audit-actions.ts +3 -0
  144. package/template/packages/modules/src/system-ai-api-bindings.ts +15 -0
  145. package/template/packages/modules/src/system-audit-actions.ts +15 -0
  146. package/template/packages/server-sdk/index.ts +52 -0
  147. package/template/packages/server-sdk/package.json +5 -1
  148. package/template/packages/server-sdk/src/client-agent-session.ts +13 -1
  149. package/template/packages/server-sdk/src/client-agent-workflow-artifact.contract.test.ts +113 -0
  150. package/template/packages/server-sdk/src/client-agent-workflow-artifact.ts +183 -0
  151. package/template/packages/server-sdk/src/durable-handler.ts +7 -0
  152. package/template/packages/server-sdk/src/request-context.ts +7 -0
  153. package/template/packages/server-sdk/src/workflow-input-artifact-port.ts +104 -0
  154. package/template/packages/server-sdk/src/workflow-input-artifact-validation.contract.test.ts +124 -0
  155. package/template/packages/server-sdk/src/workflow-input-artifact-validation.ts +347 -0
  156. package/template/packages/server-sdk/src/workflow-input-materialization-handler.ts +43 -0
  157. package/template/packages/storage/package.json +1 -1
  158. package/template/packages/storage/src/blob-store-put-once.test.ts +70 -0
  159. package/template/packages/storage/src/blob-store.ts +9 -0
  160. package/template/packages/storage/src/local-file-blob-put-once.ts +120 -0
  161. package/template/packages/storage/src/local-file-blob-store.ts +53 -1
  162. package/template/packages/storage/src/memory-blob-store.ts +73 -33
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-windy",
3
- "version": "0.6.14",
3
+ "version": "0.6.16",
4
4
  "description": "创建单组织、单租户、私有部署优先的 Windy 企业 Dashboard Starter",
5
5
  "type": "module",
6
6
  "bin": {
@@ -44,8 +44,8 @@ description: "在 Windy 或 create-windy 生成项目中新增或修改业务 Mo
44
44
  - 流式写入和大报告分别使用可选 Storage Writer / Report Staging Port、显式 policy
45
45
  与 `require*()` helper;Tool 不继承 Writer,缺少 `system.storage` 时直接说明物理
46
46
  裁剪边界。
47
- **完成标准:业务代码未注入 UploadRepository、BlobStore、Secret locator 或平台
48
- manage Repository,缺失 Port 会稳定 fail closed。**
47
+ **完成标准:业务代码未注入 UploadRepository、BlobStore、Secret locator 或平台
48
+ manage Repository,缺失 Port 会稳定 fail closed。**
49
49
  7. 业务资源含敏感字段时,在 Manifest 声明 Data Policy、standard read、
50
50
  sensitive-disclosure reveal、governed-export Permission 和对应 API,再用
51
51
  `registerGovernedResources()` 注册对象授权、脱敏投影、受控字段读取与分页 Source。
@@ -67,17 +67,66 @@ description: "在 Windy 或 create-windy 生成项目中新增或修改业务 Mo
67
67
  和可选 readiness pin。`system.agent` 的面向用户回答使用平台 Supervisor typed
68
68
  final-answer 契约;Durable Handler 只通过 Actor-aware 后台 AI Port 恢复同一幂等
69
69
  Run。浏览器或桌面宿主本地编排模型循环时使用 governed Client Agent Session,不向
70
- 客户端下发 Provider Secret 或 execution grant。**完成标准:Operation 的声明、
71
- Handler、发布坐标、输出投影和运行边界一致。**
70
+ 客户端下发 Provider Secret 或 execution grant。Workflow 需要 PageAgent 产物时复用固定
71
+ `system.agent-workflow.client-agent-artifact` Job taskKey,并在 project-owned Composition
72
+ 登记唯一 Workflow/Node/Operation/Artifact binding;业务 Handler 不直接取得 Client Agent
73
+ Session、Host Auth、BlobStore 或内部 Durable Context Port。Workflow 专用 Operation 声明
74
+ `launchAuthority="workflow-artifact"`;binding 登记 scope contract、project launch、
75
+ client task 与 terminal authority 的静态 version/digest。受保护 Workflow 只能走未暴露为
76
+ 通用路由的 `agentWorkflowProjects` Seam;通用 release/run 即使 digest 相同也 fail closed。
77
+ claim 的 raw input Artifact 只在 Host 当前重授权后交给 client task authority,Web/Extension
78
+ 只消费 ai-client 严格解析后的有界 canonical task,不自行拼 ArtifactRef、locator 或 prompt。
79
+ 模块把 Storage/JSON 作为下游 Workflow input 时,Route 先经两阶段
80
+ `workflowInputMaterialization.begin/resolve` 等待真实 Job succeeded,Handler 再用
81
+ `workflowInputArtifacts` 正式发布,不得把 fileId 当 artifactId;固定 Manifest task 只注册
82
+ 同 key/feature/permission/audit 的 Durable Handler,不额外注册 scheduled no-op。
83
+ **完成标准:Operation 的声明、
84
+ Handler、固定 taskKey、项目绑定、发布坐标、输出投影和运行边界一致。**
72
85
  11. 补齐 Manifest/Composition、Repository/Service、Guard 拒绝和 Handler 契约覆盖;按
73
86
  条件覆盖 Durable 重试/恢复、Tool 未授权发现/调用、资源文件、Secret、治理导出、
74
- 可选模块裁剪、Sandbox 隔离和 AI 恢复。**完成标准:每个新增能力至少有成功路径、
75
- 关键拒绝路径和声明/实现漂移检查。**
87
+ 可选模块裁剪、Sandbox 隔离和 AI 恢复。Client Agent Workflow 还要覆盖 pending/fence
88
+ 原子提交、短期 claim、稳定 receipt/Blob putOnce/Durable publishArtifactOnce、两阶段取消与
89
+ 重启恢复。**完成标准:每个新增能力至少有成功路径、关键拒绝路径和声明/实现漂移检查。**
76
90
  12. 更新长期文档,并按当前仓库 `AGENTS.md` 与 `package.json` 分层验证。windy-platform
77
91
  普通小提交只运行允许的静态检查;合并候选统一运行 `test:merge`,冻结发版候选才运行
78
92
  一次 `release:verify`。仅当项目提供 `lint` 脚本时运行 lint;一个 cohesive slice 一个
79
93
  Conventional Commit。**完成标准:验证层级、未执行项和提交 SHA 均可审计。**
80
94
 
95
+ ## Client Agent Workflow 重复故障解法
96
+
97
+ - 跨层 opaque ID 必须选一个 canonical prefix,并同步 Server 生成器、public SDK、客户端
98
+ validator、样例与文档;不要用“兼容多个前缀”隐藏漂移。
99
+ - 新增 Drizzle Migration 必须同步 SQL、schema、journal 和 snapshot;snapshot `prevId` 精确指向
100
+ 前一 snapshot `id`,并用仓库锁定版本独立再生成比对。
101
+ - Workflow fence 必须约束每一次 Provider attempt:预留前、出网紧前和在途期间从 canonical
102
+ Run/Node/command/generation/fence 与当前授权复核;撤销或 checker 不确定时先 revoke Session。
103
+ - Session finalize 到 Blob/bridge complete 是明确崩溃窗;恢复只读 completed Session 的 receipt、
104
+ final result 和 terminal digest,确定性 `putOnce`/complete,禁止等待浏览器或重放 Provider。
105
+ - completed replay 必须持久化并比较 `receiptIds + result` 的 canonical terminal request digest;
106
+ 完全一致才幂等,任何漂移 fail closed。
107
+ - completed replay 使用 terminal-safe authorization:继续复核当前 Actor/RBAC/Scope、claim/Host/
108
+ Session 与原 Release/Definition/command 不可变坐标,但不能重新要求 command 为 dispatched 或
109
+ Run active;撤权隐藏、摘要漂移冲突。
110
+ - Workflow PageAgent project binding 必须 pin 审核 Release 的 `releaseDigest`;loader 验证固定
111
+ taskKey Job 唯一,且只有无 branch/fallback/skip 的 `job -> artifact -> final` 直接链,Artifact
112
+ kind/policy 与绑定一致。新 Release 必须重新审核,不能静默继承旧权限。
113
+ - 不要用当前 scope digest 等于冻结 Run digest 作为多案授权证明。Host 读取持久 Job scope,验证
114
+ 当前 canonical Actor scope 包含冻结范围;只允许当前更宽或相等,不恢复冻结范围以外权限。
115
+ - terminal authority 的 binding digest 只包含稳定 version/digest,不包含函数;拒绝发生在 Session
116
+ completed transition 前;它必须收到 terminal-time 重新授权并打开的 canonical JSON input artifacts,
117
+ 不能只看 output schema。普通 Run/Session create 必须同时拒绝 workflow-only Operation。
118
+ - project launch authority 必须接收 Host capture 的 `currentAuthorizationScope`,只返回它的收窄范围;
119
+ Host 再做 containment。业务代码不得硬编码或猜 tenant、roleIds、grants。
120
+ - pre-start Workflow input 物化必须是两阶段:Request Context coordinator `begin` 提交固定模块
121
+ materializer 的真实 Durable Job,`resolve` 仅在 succeeded 且 publication/source metadata 完全匹配
122
+ 时返回 refs;begin、Durable payload 与 worker publication 共用 strict parser,mode 字段互斥且拒绝
123
+ unknown/unsafe/超预算 JSON。ready 后业务 prepare 仍重读 sourceIds 并逐项匹配,Workflow start
124
+ 再复验 Artifact。
125
+ - Targeted patch 新增 project-owned seam 时,连续 recipe 必须覆盖 public source 缺失、精确旧默认空、
126
+ 精确新默认空和客户修改四态。binding 用完整字节白名单;Composition 的 import/type/runtime property
127
+ 作为原子唯一锚点组。任何部分装配、重复、换行/注释漂移或非空客户 binding 都在写入前 fail closed;
128
+ 未选择所属可选模块时不得注入 seam。candidate regression 固定以上一公开版本为直接来源。
129
+
81
130
  ## 项目所有权与自动装配
82
131
 
83
132
  - `apps/web/src`、`apps/server/src/app`、业务模块源码和 `windy.modules.json` 都是
@@ -98,7 +147,8 @@ description: "在 Windy 或 create-windy 生成项目中新增或修改业务 Mo
98
147
  `packages/server-sdk/src/request-context.ts`、`packages/server-sdk/src/durable-handler.ts`
99
148
  - Tool 与 AI:`packages/server-sdk/src/tool-registration.ts`、
100
149
  `packages/server-sdk/src/sandbox-tool.ts`、
101
- `packages/server-sdk/src/ai-operation-registration.ts`
150
+ `packages/server-sdk/src/ai-operation-registration.ts`、
151
+ `packages/server-sdk/src/client-agent-workflow-artifact.ts`
102
152
  - Secret、Storage 与治理:`packages/server-sdk/src/secret-port.ts`、
103
153
  `packages/server-sdk/src/secret-binding.ts`、`packages/server-sdk/src/storage-port.ts`、
104
154
  `packages/server-sdk/src/report-staging-port.ts`、
@@ -111,7 +161,7 @@ description: "在 Windy 或 create-windy 生成项目中新增或修改业务 Mo
111
161
  - 条件能力:`docs/architecture/module-secret-resolution.md`、
112
162
  `docs/architecture/report-staging.md`、`docs/platform/governed-export-renderers.md`、
113
163
  `docs/platform/agent-tool-approval.md`、`docs/platform/background-ai-operations.md`、
114
- `docs/platform/client-agent-session.md`
164
+ `docs/platform/client-agent-session.md`、`docs/architecture/agent-workflow.md`
115
165
 
116
166
  某个事实源或 `system.agent` / `system.storage` 不存在时,先说明当前生成组合的物理边界,
117
167
  不在业务层补一套替代实现。
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "schemaVersion": 1,
3
- "templateCommit": "74fc88e548934dbda773291c4ad078d16a5396d1",
4
- "generatorVersion": "0.6.14",
3
+ "templateCommit": "8e1d09815139887e150b7ed51d5b974879b1ec54",
4
+ "generatorVersion": "0.6.16",
5
5
  "modules": [
6
6
  {
7
7
  "name": "system",
@@ -0,0 +1,162 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import { createFixture, validCompletion } from "./service.test-support.js";
3
+ import { consumeClientAgentCompletion } from "./completion.js";
4
+
5
+ describe("Client Session Provider attempt authority", () => {
6
+ test("普通 Session 经 Host checker 明确分类后保持原 completion 行为", async () => {
7
+ const fixture = await createFixture({
8
+ attemptAuthority: {
9
+ async check() {
10
+ return "ordinary";
11
+ },
12
+ },
13
+ });
14
+
15
+ await expect(fixture.complete(validCompletion())).resolves.toBeDefined();
16
+ expect(fixture.provider.requests).toHaveLength(1);
17
+ expect(
18
+ (await fixture.repository.find(fixture.grant.sessionId))?.status,
19
+ ).toBe("active");
20
+ });
21
+
22
+ test("每个 Workflow Session attempt 出网前复核,漂移先撤销且不调用 Provider", async () => {
23
+ let decision = "current" as "current" | "revoked";
24
+ let checks = 0;
25
+ const fixture = await createFixture({
26
+ attemptAuthority: {
27
+ async check(facts) {
28
+ checks += 1;
29
+ expect(facts).toMatchObject({
30
+ actorId: "actor-1",
31
+ operationKey: "support.answer",
32
+ });
33
+ return decision;
34
+ },
35
+ },
36
+ });
37
+
38
+ await fixture.complete(validCompletion());
39
+ decision = "revoked";
40
+ await expect(fixture.complete(validCompletion())).rejects.toMatchObject({
41
+ code: "CONFLICT",
42
+ });
43
+
44
+ expect(checks).toBe(4);
45
+ expect(fixture.provider.requests).toHaveLength(1);
46
+ expect(
47
+ (await fixture.repository.find(fixture.grant.sessionId))?.status,
48
+ ).toBe("revoked");
49
+ });
50
+
51
+ test("reserve 后 authority 漂移仍在真正 Provider 出网紧前阻断", async () => {
52
+ let checks = 0;
53
+ const fixture = await createFixture({
54
+ attemptAuthority: {
55
+ async check() {
56
+ checks += 1;
57
+ return checks === 1 ? "current" : "revoked";
58
+ },
59
+ },
60
+ });
61
+
62
+ await expect(fixture.complete(validCompletion())).rejects.toMatchObject({
63
+ code: "CONFLICT",
64
+ });
65
+ expect(checks).toBe(2);
66
+ expect(fixture.provider.requests).toHaveLength(0);
67
+ expect(
68
+ (await fixture.repository.find(fixture.grant.sessionId))?.status,
69
+ ).toBe("revoked");
70
+ });
71
+
72
+ test("reserve 后当前授权漂移也在真正 Provider 出网紧前阻断", async () => {
73
+ let revokeCurrentAuthorization: () => void = () => undefined;
74
+ const fixture = await createFixture({
75
+ attemptAuthority: {
76
+ async check() {
77
+ revokeCurrentAuthorization();
78
+ return "current";
79
+ },
80
+ },
81
+ });
82
+ revokeCurrentAuthorization = () => {
83
+ fixture.admission.allowed = false;
84
+ };
85
+
86
+ await expect(fixture.complete(validCompletion())).rejects.toMatchObject({
87
+ code: "CONFLICT",
88
+ });
89
+ expect(fixture.provider.requests).toHaveLength(0);
90
+ expect(
91
+ (await fixture.repository.find(fixture.grant.sessionId))?.status,
92
+ ).toBe("revoked");
93
+ });
94
+
95
+ test("reserve 后 Session 被 takeover 撤销时,generator 不得开始 Provider", async () => {
96
+ const fixture = await createFixture({
97
+ attemptAuthority: {
98
+ async check() {
99
+ return "current";
100
+ },
101
+ },
102
+ });
103
+ const completion = await fixture.service.startCompletion(
104
+ fixture.grant.sessionId,
105
+ validCompletion(),
106
+ fixture.caller,
107
+ );
108
+ await fixture.repository.transition(
109
+ fixture.grant.sessionId,
110
+ "active",
111
+ "revoked",
112
+ );
113
+
114
+ await expect(
115
+ consumeClientAgentCompletion(completion.events),
116
+ ).rejects.toMatchObject({ code: "CONFLICT" });
117
+ expect(fixture.provider.requests).toHaveLength(0);
118
+ });
119
+
120
+ test("Provider 返回后 fence 漂移时不提交迟到 receipt", async () => {
121
+ let decision = "current" as "current" | "revoked";
122
+ const fixture = await createFixture({
123
+ attemptAuthority: {
124
+ async check() {
125
+ return decision;
126
+ },
127
+ },
128
+ execute: async () => {
129
+ decision = "revoked";
130
+ return {
131
+ choices: [{ message: { role: "assistant", content: "late" } }],
132
+ };
133
+ },
134
+ });
135
+
136
+ await expect(fixture.complete(validCompletion())).rejects.toMatchObject({
137
+ code: "CONFLICT",
138
+ });
139
+ expect(
140
+ (await fixture.repository.find(fixture.grant.sessionId))?.receipts,
141
+ ).toHaveLength(0);
142
+ expect(fixture.provider.requests).toHaveLength(1);
143
+ });
144
+
145
+ test("authority checker 异常同样 fail closed 且不产生 Provider attempt", async () => {
146
+ const fixture = await createFixture({
147
+ attemptAuthority: {
148
+ async check() {
149
+ throw new Error("database unavailable");
150
+ },
151
+ },
152
+ });
153
+
154
+ await expect(fixture.complete(validCompletion())).rejects.toMatchObject({
155
+ code: "CONFLICT",
156
+ });
157
+ expect(fixture.provider.requests).toHaveLength(0);
158
+ expect(
159
+ (await fixture.repository.find(fixture.grant.sessionId))?.status,
160
+ ).toBe("revoked");
161
+ });
162
+ });
@@ -0,0 +1,191 @@
1
+ import type {
2
+ ClientSessionAdmissionPort,
3
+ ClientSessionAttemptAuthorityChecker,
4
+ ClientSessionAttemptAuthorityDecision,
5
+ ClientSessionRecord,
6
+ } from "./contracts.js";
7
+ import type { NodePgDatabase } from "drizzle-orm/node-postgres";
8
+ import { clientSessionConflict } from "./errors.js";
9
+ import type { ClientSessionRepository } from "./repository.js";
10
+
11
+ export type ClientSessionAttemptRevocationCause =
12
+ | "authorization-drift"
13
+ | "authorization-check-failed"
14
+ | "ttl"
15
+ | "workflow-authority"
16
+ | "workflow-authority-check-failed";
17
+
18
+ export interface ClientSessionAttemptAuthorityHostChecker extends ClientSessionAttemptAuthorityChecker {
19
+ checkTransaction?(
20
+ transaction: NodePgDatabase,
21
+ facts: Parameters<ClientSessionAttemptAuthorityChecker["check"]>[0],
22
+ now: number,
23
+ ): Promise<ClientSessionAttemptAuthorityDecision>;
24
+ }
25
+
26
+ /** 启动时先创建 binding,Workflow runtime 完成装配后再绑定唯一 Host checker。 */
27
+ export class ClientSessionAttemptAuthorityBinding implements ClientSessionAttemptAuthorityChecker {
28
+ #checker?: ClientSessionAttemptAuthorityHostChecker;
29
+
30
+ bind(checker: ClientSessionAttemptAuthorityHostChecker): () => void {
31
+ if (this.#checker) {
32
+ throw new Error("Client Session attempt authority checker 重复绑定");
33
+ }
34
+ this.#checker = checker;
35
+ return () => {
36
+ if (this.#checker === checker) this.#checker = undefined;
37
+ };
38
+ }
39
+
40
+ check(
41
+ facts: Parameters<ClientSessionAttemptAuthorityChecker["check"]>[0],
42
+ signal?: AbortSignal,
43
+ ): Promise<ClientSessionAttemptAuthorityDecision> {
44
+ return this.#checker?.check(facts, signal) ?? Promise.resolve("ordinary");
45
+ }
46
+
47
+ checkTransaction(
48
+ transaction: NodePgDatabase,
49
+ facts: Parameters<ClientSessionAttemptAuthorityChecker["check"]>[0],
50
+ now: number,
51
+ ) {
52
+ return this.#checker?.checkTransaction
53
+ ? this.#checker.checkTransaction(transaction, facts, now)
54
+ : (this.#checker?.check(facts) ?? Promise.resolve("ordinary" as const));
55
+ }
56
+ }
57
+
58
+ export async function enforceClientSessionAttemptAuthority(input: {
59
+ checker: ClientSessionAttemptAuthorityChecker;
60
+ record: ClientSessionRecord;
61
+ repository: ClientSessionRepository;
62
+ signal?: AbortSignal;
63
+ workflowOwned: boolean;
64
+ audit(
65
+ record: ClientSessionRecord,
66
+ cause: ClientSessionAttemptRevocationCause,
67
+ ): Promise<void> | void;
68
+ }): Promise<boolean> {
69
+ let decision: ClientSessionAttemptAuthorityDecision;
70
+ try {
71
+ decision = await input.checker.check(
72
+ clientSessionAttemptAuthorityFacts(input.record),
73
+ input.signal,
74
+ );
75
+ } catch {
76
+ await revoke(input, "workflow-authority-check-failed");
77
+ throw clientSessionConflict("Client Agent 会话 authority 校验失败");
78
+ }
79
+ if (decision === "current") return true;
80
+ if (decision === "ordinary" && !input.workflowOwned) return false;
81
+ await revoke(input, "workflow-authority");
82
+ throw clientSessionConflict("Client Agent 会话 authority 已失效");
83
+ }
84
+
85
+ export async function enforceClientSessionCurrentAuthorization(input: {
86
+ admission: ClientSessionAdmissionPort;
87
+ record: ClientSessionRecord;
88
+ repository: ClientSessionRepository;
89
+ attempt?: number;
90
+ now: number;
91
+ audit(
92
+ record: ClientSessionRecord,
93
+ cause: ClientSessionAttemptRevocationCause,
94
+ ): Promise<void> | void;
95
+ }) {
96
+ const canonical = await input.repository.find(input.record.sessionId);
97
+ if (!canonical || canonical.status !== "active") {
98
+ throw clientSessionConflict("Client Agent 会话已关闭");
99
+ }
100
+ if (
101
+ input.attempt !== undefined &&
102
+ !canonical.pendingAttempts.includes(input.attempt)
103
+ ) {
104
+ await revokeCurrentAuthorization(input, canonical);
105
+ throw clientSessionConflict("Client Agent attempt 已失效");
106
+ }
107
+ let cause: ClientSessionAttemptRevocationCause | undefined;
108
+ if (canonical.expiresAt <= input.now) {
109
+ cause = "ttl";
110
+ } else {
111
+ try {
112
+ cause = (await input.admission.reauthorize(canonical, canonical.actorId))
113
+ ? undefined
114
+ : "authorization-drift";
115
+ } catch {
116
+ cause = "authorization-check-failed";
117
+ }
118
+ }
119
+ if (!cause) return;
120
+ const status = cause === "ttl" ? "expired" : "revoked";
121
+ const closed = await input.repository.transition(
122
+ canonical.sessionId,
123
+ "active",
124
+ status,
125
+ );
126
+ if (closed) await input.audit(closed, cause);
127
+ throw clientSessionConflict("Client Agent 会话当前授权已失效");
128
+ }
129
+
130
+ export async function enforceClientSessionProviderBoundary(input: {
131
+ admission: ClientSessionAdmissionPort;
132
+ checker: ClientSessionAttemptAuthorityChecker;
133
+ record: ClientSessionRecord;
134
+ repository: ClientSessionRepository;
135
+ attempt?: number;
136
+ now: number;
137
+ signal?: AbortSignal;
138
+ workflowOwned: boolean;
139
+ audit(
140
+ record: ClientSessionRecord,
141
+ cause: ClientSessionAttemptRevocationCause,
142
+ ): Promise<void> | void;
143
+ }) {
144
+ await enforceClientSessionCurrentAuthorization(input);
145
+ if (!input.workflowOwned) return;
146
+ await enforceClientSessionAttemptAuthority({
147
+ checker: input.checker,
148
+ record: input.record,
149
+ repository: input.repository,
150
+ ...(input.signal ? { signal: input.signal } : {}),
151
+ workflowOwned: true,
152
+ audit: input.audit,
153
+ });
154
+ }
155
+
156
+ async function revokeCurrentAuthorization(
157
+ input: Parameters<typeof enforceClientSessionCurrentAuthorization>[0],
158
+ canonical: ClientSessionRecord,
159
+ ) {
160
+ const revoked = await input.repository.transition(
161
+ canonical.sessionId,
162
+ "active",
163
+ "revoked",
164
+ );
165
+ if (revoked) await input.audit(revoked, "authorization-check-failed");
166
+ }
167
+
168
+ export function clientSessionAttemptAuthorityFacts(
169
+ record: ClientSessionRecord,
170
+ ) {
171
+ return {
172
+ sessionId: record.sessionId,
173
+ clientRunId: record.clientRunId,
174
+ actorId: record.actorId,
175
+ operationKey: record.operationKey,
176
+ clientBindingDigest: record.clientBindingDigest,
177
+ hostSessionDigest: record.hostSessionDigest,
178
+ };
179
+ }
180
+
181
+ async function revoke(
182
+ input: Parameters<typeof enforceClientSessionAttemptAuthority>[0],
183
+ cause: ClientSessionAttemptRevocationCause,
184
+ ) {
185
+ const revoked = await input.repository.transition(
186
+ input.record.sessionId,
187
+ "active",
188
+ "revoked",
189
+ );
190
+ if (revoked) await input.audit(revoked, cause);
191
+ }
@@ -21,6 +21,7 @@ import {
21
21
  createClientAgentHostAuthRouteApp,
22
22
  hostSessionDigest,
23
23
  } from "./host-auth.js";
24
+ import { ClientSessionAttemptAuthorityBinding } from "./attempt-authority.js";
24
25
 
25
26
  export function createClientAgentSessionRuntime(input: {
26
27
  app: RouteApp;
@@ -38,12 +39,21 @@ export function createClientAgentSessionRuntime(input: {
38
39
  registerUnavailableClientAgentSessionRoutes(
39
40
  createManifestAuthorizedRouteApp(input.app, input.routeAuthorizer),
40
41
  );
41
- return { service: undefined, dispose: undefined };
42
+ return {
43
+ service: undefined,
44
+ repository: undefined,
45
+ hostApp: undefined,
46
+ attemptAuthority: undefined,
47
+ revokeHost: undefined,
48
+ dispose: undefined,
49
+ };
42
50
  }
51
+ const attemptAuthority = new ClientSessionAttemptAuthorityBinding();
52
+ const repository = input.db
53
+ ? new DrizzleClientSessionRepository(input.db, attemptAuthority)
54
+ : new InMemoryClientSessionRepository();
43
55
  const service = new GovernedClientAgentSessionService(
44
- input.db
45
- ? new DrizzleClientSessionRepository(input.db)
46
- : new InMemoryClientSessionRepository(),
56
+ repository,
47
57
  new ManifestClientSessionAdmission(
48
58
  input.modules,
49
59
  input.hosts,
@@ -64,6 +74,7 @@ export function createClientAgentSessionRuntime(input: {
64
74
  }),
65
75
  new RuntimeClientSessionAudit(input.runtime),
66
76
  trustedBaseUrl(),
77
+ attemptAuthority,
67
78
  );
68
79
  const hostApp = createClientAgentHostAuthRouteApp({
69
80
  app: input.app,
@@ -75,13 +86,18 @@ export function createClientAgentSessionRuntime(input: {
75
86
  createManifestAuthorizedRouteApp(hostApp, input.routeAuthorizer),
76
87
  service,
77
88
  );
78
- const unbind = input.hostAuth.bindRevocations(async (principal) => {
79
- await service.revokeHostSession(
80
- hostSessionDigest(principal),
81
- principal.actorId,
82
- );
83
- });
84
- return { service, dispose: unbind };
89
+ return {
90
+ service,
91
+ repository,
92
+ hostApp,
93
+ attemptAuthority,
94
+ revokeHost: (principal: Parameters<typeof hostSessionDigest>[0]) =>
95
+ service.revokeHostSession(
96
+ hostSessionDigest(principal),
97
+ principal.actorId,
98
+ ),
99
+ dispose: undefined,
100
+ };
85
101
  }
86
102
 
87
103
  function trustedBaseUrl(): string {