@yss123/cage-cli 0.2.0 → 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 (607) hide show
  1. package/README.md +47 -3
  2. package/bin/cage.js +5 -0
  3. package/bin/create-cage-app.js +5 -0
  4. package/dist/analyze.d.ts +16 -0
  5. package/dist/analyze.js +18 -2
  6. package/dist/analyze.js.map +1 -1
  7. package/dist/baseline.d.ts +8 -0
  8. package/dist/baseline.js +13 -0
  9. package/dist/baseline.js.map +1 -1
  10. package/dist/bootstrap.d.ts +20 -0
  11. package/dist/bootstrap.js +75 -0
  12. package/dist/bootstrap.js.map +1 -0
  13. package/dist/budget.d.ts +7 -0
  14. package/dist/budget.js +49 -5
  15. package/dist/budget.js.map +1 -1
  16. package/dist/capabilities.d.ts +38 -0
  17. package/dist/capabilities.js +133 -0
  18. package/dist/capabilities.js.map +1 -0
  19. package/dist/config.d.ts +31 -2
  20. package/dist/config.js +22 -0
  21. package/dist/config.js.map +1 -1
  22. package/dist/context.d.ts +14 -0
  23. package/dist/context.js +35 -2
  24. package/dist/context.js.map +1 -1
  25. package/dist/create-prompt.d.ts +7 -0
  26. package/dist/create-prompt.js +12 -0
  27. package/dist/create-prompt.js.map +1 -1
  28. package/dist/create.d.ts +21 -1
  29. package/dist/create.js +23 -1
  30. package/dist/create.js.map +1 -1
  31. package/dist/derive.d.ts +23 -0
  32. package/dist/derive.js +40 -0
  33. package/dist/derive.js.map +1 -1
  34. package/dist/dev.d.ts +6 -0
  35. package/dist/dev.js +11 -0
  36. package/dist/dev.js.map +1 -1
  37. package/dist/doctor.d.ts +98 -0
  38. package/dist/doctor.js +93 -0
  39. package/dist/doctor.js.map +1 -0
  40. package/dist/env.d.ts +6 -0
  41. package/dist/env.js +11 -0
  42. package/dist/env.js.map +1 -1
  43. package/dist/findings.d.ts +27 -0
  44. package/dist/findings.js +20 -0
  45. package/dist/findings.js.map +1 -1
  46. package/dist/gen/contracts.d.ts +28 -0
  47. package/dist/gen/contracts.js +52 -12
  48. package/dist/gen/contracts.js.map +1 -1
  49. package/dist/gen/index.d.ts +26 -0
  50. package/dist/gen/index.js +69 -1
  51. package/dist/gen/index.js.map +1 -1
  52. package/dist/gen/zod-code.d.ts +42 -0
  53. package/dist/gen/zod-code.js +178 -16
  54. package/dist/gen/zod-code.js.map +1 -1
  55. package/dist/graph-approvals.d.ts +60 -0
  56. package/dist/graph-approvals.js +80 -0
  57. package/dist/graph-approvals.js.map +1 -0
  58. package/dist/graph-business.d.ts +135 -0
  59. package/dist/graph-business.js +404 -0
  60. package/dist/graph-business.js.map +1 -0
  61. package/dist/graph-command.d.ts +17 -0
  62. package/dist/graph-command.js +21 -0
  63. package/dist/graph-command.js.map +1 -1
  64. package/dist/graph-model.d.ts +44 -0
  65. package/dist/graph-model.js +36 -3
  66. package/dist/graph-model.js.map +1 -1
  67. package/dist/graph-presentation.d.ts +16 -0
  68. package/dist/graph-presentation.js +10 -1
  69. package/dist/graph-presentation.js.map +1 -1
  70. package/dist/graph-query.d.ts +95 -0
  71. package/dist/graph-query.js +62 -0
  72. package/dist/graph-query.js.map +1 -1
  73. package/dist/graph-relations.d.ts +20 -0
  74. package/dist/graph-relations.js +34 -4
  75. package/dist/graph-relations.js.map +1 -1
  76. package/dist/graph-search.d.ts +35 -0
  77. package/dist/graph-search.js +36 -0
  78. package/dist/graph-search.js.map +1 -1
  79. package/dist/graph-snapshot.d.ts +26 -0
  80. package/dist/graph-snapshot.js +30 -1
  81. package/dist/graph-snapshot.js.map +1 -1
  82. package/dist/graph.d.ts +13 -2
  83. package/dist/graph.js +59 -4
  84. package/dist/graph.js.map +1 -1
  85. package/dist/impact.d.ts +50 -0
  86. package/dist/impact.js +62 -2
  87. package/dist/impact.js.map +1 -1
  88. package/dist/index.d.ts +10 -0
  89. package/dist/index.js +10 -0
  90. package/dist/index.js.map +1 -1
  91. package/dist/init.d.ts +12 -0
  92. package/dist/init.js +15 -0
  93. package/dist/init.js.map +1 -1
  94. package/dist/load.d.ts +10 -0
  95. package/dist/load.js +15 -0
  96. package/dist/load.js.map +1 -1
  97. package/dist/main.d.ts +6 -0
  98. package/dist/main.js +93 -5
  99. package/dist/main.js.map +1 -1
  100. package/dist/migrate.d.ts +12 -0
  101. package/dist/migrate.js +17 -0
  102. package/dist/migrate.js.map +1 -1
  103. package/dist/ontology.d.ts +67 -2
  104. package/dist/ontology.js +89 -2
  105. package/dist/ontology.js.map +1 -1
  106. package/dist/process-rules.d.ts +21 -0
  107. package/dist/process-rules.js +100 -0
  108. package/dist/process-rules.js.map +1 -0
  109. package/dist/process.d.ts +32 -0
  110. package/dist/process.js +32 -0
  111. package/dist/process.js.map +1 -1
  112. package/dist/project-structure.d.ts +5 -0
  113. package/dist/project-structure.js +10 -0
  114. package/dist/project-structure.js.map +1 -1
  115. package/dist/registration.d.ts +16 -0
  116. package/dist/registration.js +56 -0
  117. package/dist/registration.js.map +1 -0
  118. package/dist/rules.d.ts +52 -0
  119. package/dist/rules.js +58 -0
  120. package/dist/rules.js.map +1 -1
  121. package/dist/runtime-diagnostics.d.ts +27 -0
  122. package/dist/runtime-diagnostics.js +60 -0
  123. package/dist/runtime-diagnostics.js.map +1 -0
  124. package/dist/snapshots.d.ts +40 -0
  125. package/dist/snapshots.js +62 -0
  126. package/dist/snapshots.js.map +1 -0
  127. package/dist/static-check.d.ts +33 -0
  128. package/dist/static-check.js +37 -1
  129. package/dist/static-check.js.map +1 -1
  130. package/dist/sync.d.ts +11 -0
  131. package/dist/sync.js +65 -8
  132. package/dist/sync.js.map +1 -1
  133. package/dist/upgrade.d.ts +16 -0
  134. package/dist/upgrade.js +21 -0
  135. package/dist/upgrade.js.map +1 -1
  136. package/dist/verify.d.ts +14 -0
  137. package/dist/verify.js +19 -0
  138. package/dist/verify.js.map +1 -1
  139. package/dist/version.js +5 -0
  140. package/dist/version.js.map +1 -1
  141. package/graph-ui/app.js +321 -27
  142. package/graph-ui/approval-flow.js +165 -0
  143. package/graph-ui/business.css +985 -0
  144. package/graph-ui/business.js +549 -0
  145. package/graph-ui/flowchart-interaction.js +177 -0
  146. package/graph-ui/flowchart.js +1059 -0
  147. package/graph-ui/graph-approvals.js +79 -0
  148. package/graph-ui/graph-business.js +403 -0
  149. package/graph-ui/graph-model.js +36 -3
  150. package/graph-ui/graph-relations.js +34 -4
  151. package/graph-ui/graph-search.js +36 -0
  152. package/graph-ui/index.html +26 -12
  153. package/graph-ui/model.js +28 -0
  154. package/graph-ui/relations.js +4 -0
  155. package/graph-ui/structure.js +44 -0
  156. package/graph-ui/style.css +12 -0
  157. package/package.json +6 -4
  158. package/templates/app/.dockerignore +2 -0
  159. package/templates/app/.env.example +10 -0
  160. package/templates/app/AGENTS.md +49 -9
  161. package/templates/app/README.md +4 -1
  162. package/templates/app/_gitignore +1 -0
  163. package/templates/app/apps/api/package.json +2 -2
  164. package/templates/app/apps/api/src/main.ts +5 -1
  165. package/templates/app/apps/api/src/worker.ts +3 -0
  166. package/templates/app/apps/web/index.html +1 -0
  167. package/templates/app/apps/web/package.json +7 -3
  168. package/templates/app/apps/web/src/app/app.tsx +122 -12
  169. package/templates/app/apps/web/src/app/styles/global.css +88 -8
  170. package/templates/app/apps/web/src/app/use-session.ts +43 -0
  171. package/templates/app/apps/web/src/app/workspace-layout.tsx +58 -19
  172. package/templates/app/apps/web/src/main.tsx +5 -0
  173. package/templates/app/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +11 -0
  174. package/templates/app/apps/web/src/modules/approval/index.ts +4 -0
  175. package/templates/app/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +20 -1
  176. package/templates/app/apps/web/src/modules/auth/index.ts +4 -0
  177. package/templates/app/apps/web/src/modules/auth/pages/login-page.tsx +12 -0
  178. package/templates/app/apps/web/src/modules/inventory/index.ts +4 -0
  179. package/templates/app/apps/web/src/modules/inventory/pages/inventory-page.tsx +29 -1
  180. package/templates/app/apps/web/src/modules/purchase/components/approval-history.tsx +32 -0
  181. package/templates/app/apps/web/src/modules/purchase/components/order-attachments.tsx +29 -0
  182. package/templates/app/apps/web/src/modules/purchase/components/purchase-assistant-context.tsx +17 -0
  183. package/templates/app/apps/web/src/modules/purchase/components/purchase-order-form.css +6 -0
  184. package/templates/app/apps/web/src/modules/purchase/components/purchase-order-form.tsx +25 -3
  185. package/templates/app/apps/web/src/modules/purchase/index.ts +5 -0
  186. package/templates/app/apps/web/src/modules/purchase/lib/presentation.ts +20 -0
  187. package/templates/app/apps/web/src/modules/purchase/pages/purchase-intelligence.css +15 -0
  188. package/templates/app/apps/web/src/modules/purchase/pages/purchase-order-detail-page.tsx +154 -0
  189. package/templates/app/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +52 -22
  190. package/templates/app/apps/web/src/modules/workspace/index.ts +5 -0
  191. package/templates/app/apps/web/src/modules/workspace/pages/home-page.tsx +44 -0
  192. package/templates/app/apps/web/src/shared/hooks/use-command.ts +13 -0
  193. package/templates/app/apps/web/src/shared/lib/error-message.ts +9 -0
  194. package/templates/app/apps/web/src/shared/lib/navigation.ts +21 -0
  195. package/templates/app/apps/web/src/shared/ui/assistant-markdown.tsx +19 -0
  196. package/templates/app/apps/web/src/shared/ui/assistant-panel.css +134 -0
  197. package/templates/app/apps/web/src/shared/ui/assistant-panel.tsx +200 -0
  198. package/templates/app/apps/web/src/shared/ui/assistant-scope.tsx +72 -0
  199. package/templates/app/apps/web/src/shared/ui/attachment-upload.css +28 -0
  200. package/templates/app/apps/web/src/shared/ui/attachment-upload.tsx +163 -0
  201. package/templates/app/apps/web/src/shared/ui/notice.tsx +7 -0
  202. package/templates/app/apps/web/src/shared/ui/page-heading.tsx +7 -0
  203. package/templates/app/apps/web/src/shared/ui/page-state.css +47 -0
  204. package/templates/app/apps/web/src/shared/ui/page-state.tsx +76 -0
  205. package/templates/app/apps/web/src/shared/ui/require-capability.tsx +34 -0
  206. package/templates/app/apps/web/vite.config.ts +5 -0
  207. package/templates/app/cage.config.json +9 -0
  208. package/templates/app/db/migrations/0002_draft.sql +12 -0
  209. package/templates/app/db/migrations/0003_draft.sql +8 -0
  210. package/templates/app/deploy/nginx.conf +52 -0
  211. package/templates/app/docs/ai/README.md +21 -0
  212. package/templates/app/docs/ai/architecture.md +26 -0
  213. package/templates/app/docs/ai/commenting.md +203 -0
  214. package/templates/app/docs/ai/diagnostics.md +80 -0
  215. package/templates/app/docs/ai/files.md +93 -0
  216. package/templates/app/docs/ai/intelligence.md +348 -0
  217. package/templates/app/docs/ai/ontology-context.md +59 -4
  218. package/templates/app/docs/ai/primitives.md +33 -0
  219. package/templates/app/docs/ai/project-structure.md +8 -3
  220. package/templates/app/docs/ai/purchase-example.md +16 -2
  221. package/templates/app/docs/ai/recipes.md +9 -1
  222. package/templates/app/docs/ai/routing.md +86 -0
  223. package/templates/app/docs/ai/troubleshooting.md +5 -0
  224. package/templates/app/docs/ai/workflow.md +24 -11
  225. package/templates/app/docs/errors/CAGE-AI-001.md +61 -0
  226. package/templates/app/docs/errors/CAGE-APPR-001.md +7 -6
  227. package/templates/app/docs/errors/CAGE-APPR-002.md +2 -2
  228. package/templates/app/docs/errors/CAGE-APPR-003.md +1 -1
  229. package/templates/app/docs/errors/CAGE-APPR-004.md +4 -4
  230. package/templates/app/docs/errors/CAGE-APPR-005.md +1 -1
  231. package/templates/app/docs/errors/CAGE-APPR-006.md +12 -6
  232. package/templates/app/docs/errors/CAGE-APPR-007.md +3 -3
  233. package/templates/app/docs/errors/CAGE-APPR-010.md +5 -5
  234. package/templates/app/docs/errors/CAGE-AUDIT-001.md +53 -0
  235. package/templates/app/docs/errors/CAGE-AUTH-001.md +1 -1
  236. package/templates/app/docs/errors/CAGE-BIZ-001.md +51 -0
  237. package/templates/app/docs/errors/CAGE-BIZ-002.md +49 -0
  238. package/templates/app/docs/errors/CAGE-BUDGET-001.md +1 -1
  239. package/templates/app/docs/errors/CAGE-BUDGET-002.md +1 -1
  240. package/templates/app/docs/errors/CAGE-BUDGET-003.md +1 -1
  241. package/templates/app/docs/errors/CAGE-CMD-002.md +1 -1
  242. package/templates/app/docs/errors/CAGE-CMD-003.md +1 -1
  243. package/templates/app/docs/errors/CAGE-CONN-001.md +3 -3
  244. package/templates/app/docs/errors/CAGE-CONN-002.md +1 -1
  245. package/templates/app/docs/errors/CAGE-CONN-003.md +1 -1
  246. package/templates/app/docs/errors/CAGE-CONN-004.md +1 -1
  247. package/templates/app/docs/errors/CAGE-CONN-005.md +1 -1
  248. package/templates/app/docs/errors/CAGE-CONN-006.md +1 -1
  249. package/templates/app/docs/errors/CAGE-CONN-007.md +6 -6
  250. package/templates/app/docs/errors/CAGE-CONN-008.md +5 -5
  251. package/templates/app/docs/errors/CAGE-CONN-009.md +6 -6
  252. package/templates/app/docs/errors/CAGE-CONN-010.md +6 -6
  253. package/templates/app/docs/errors/CAGE-CONN-011.md +1 -1
  254. package/templates/app/docs/errors/CAGE-CONN-012.md +1 -1
  255. package/templates/app/docs/errors/CAGE-CONN-013.md +49 -0
  256. package/templates/app/docs/errors/CAGE-CONN-014.md +48 -0
  257. package/templates/app/docs/errors/CAGE-CONN-015.md +49 -0
  258. package/templates/app/docs/errors/CAGE-CREATE-001.md +10 -9
  259. package/templates/app/docs/errors/CAGE-DECL-001.md +2 -2
  260. package/templates/app/docs/errors/CAGE-DECL-002.md +7 -6
  261. package/templates/app/docs/errors/CAGE-DECL-003.md +1 -1
  262. package/templates/app/docs/errors/CAGE-DECL-004.md +1 -1
  263. package/templates/app/docs/errors/CAGE-DEP-001.md +2 -2
  264. package/templates/app/docs/errors/CAGE-DEP-002.md +1 -1
  265. package/templates/app/docs/errors/CAGE-DEP-003.md +2 -2
  266. package/templates/app/docs/errors/CAGE-DEP-004.md +1 -1
  267. package/templates/app/docs/errors/CAGE-DEP-005.md +1 -1
  268. package/templates/app/docs/errors/CAGE-DIAG-001.md +53 -0
  269. package/templates/app/docs/errors/CAGE-DLQ-001.md +2 -2
  270. package/templates/app/docs/errors/CAGE-ENV-001.md +4 -4
  271. package/templates/app/docs/errors/CAGE-ENV-002.md +51 -0
  272. package/templates/app/docs/errors/CAGE-ENV-003.md +48 -0
  273. package/templates/app/docs/errors/CAGE-EVENT-001.md +1 -1
  274. package/templates/app/docs/errors/CAGE-EVENT-002.md +7 -7
  275. package/templates/app/docs/errors/CAGE-FIELD-001.md +2 -2
  276. package/templates/app/docs/errors/CAGE-FIELD-002.md +1 -1
  277. package/templates/app/docs/errors/CAGE-FIELD-003.md +1 -1
  278. package/templates/app/docs/errors/CAGE-FIELD-004.md +1 -1
  279. package/templates/app/docs/errors/CAGE-FILE-001.md +56 -0
  280. package/templates/app/docs/errors/CAGE-GEN-001.md +2 -2
  281. package/templates/app/docs/errors/CAGE-GEN-002.md +1 -1
  282. package/templates/app/docs/errors/CAGE-GEN-003.md +53 -0
  283. package/templates/app/docs/errors/CAGE-GRANT-001.md +3 -3
  284. package/templates/app/docs/errors/CAGE-GRANT-002.md +2 -2
  285. package/templates/app/docs/errors/CAGE-GRANT-003.md +7 -6
  286. package/templates/app/docs/errors/CAGE-IMPACT-001.md +2 -2
  287. package/templates/app/docs/errors/CAGE-IMPACT-002.md +52 -0
  288. package/templates/app/docs/errors/CAGE-INIT-001.md +1 -1
  289. package/templates/app/docs/errors/CAGE-INIT-002.md +1 -1
  290. package/templates/app/docs/errors/CAGE-INIT-003.md +2 -2
  291. package/templates/app/docs/errors/CAGE-INIT-004.md +1 -1
  292. package/templates/app/docs/errors/CAGE-INIT-005.md +52 -0
  293. package/templates/app/docs/errors/CAGE-JOB-001.md +1 -1
  294. package/templates/app/docs/errors/CAGE-JOB-002.md +1 -1
  295. package/templates/app/docs/errors/CAGE-JOB-003.md +1 -1
  296. package/templates/app/docs/errors/CAGE-LINT-001.md +1 -1
  297. package/templates/app/docs/errors/CAGE-MIG-001.md +1 -1
  298. package/templates/app/docs/errors/CAGE-MIG-002.md +1 -1
  299. package/templates/app/docs/errors/CAGE-MIG-005.md +1 -1
  300. package/templates/app/docs/errors/CAGE-NAME-001.md +1 -1
  301. package/templates/app/docs/errors/CAGE-NAME-002.md +1 -1
  302. package/templates/app/docs/errors/CAGE-NAME-003.md +3 -3
  303. package/templates/app/docs/errors/CAGE-NAME-004.md +1 -1
  304. package/templates/app/docs/errors/CAGE-NAME-005.md +1 -1
  305. package/templates/app/docs/errors/CAGE-NAME-006.md +1 -1
  306. package/templates/app/docs/errors/CAGE-ORG-001.md +7 -6
  307. package/templates/app/docs/errors/CAGE-PAIR-002.md +6 -5
  308. package/templates/app/docs/errors/CAGE-QUERY-001.md +1 -1
  309. package/templates/app/docs/errors/CAGE-QUERY-002.md +2 -2
  310. package/templates/app/docs/errors/CAGE-QUERY-003.md +7 -6
  311. package/templates/app/docs/errors/CAGE-REACT-001.md +3 -3
  312. package/templates/app/docs/errors/CAGE-REACT-002.md +1 -1
  313. package/templates/app/docs/errors/CAGE-REACT-003.md +50 -0
  314. package/templates/app/docs/errors/CAGE-READS-001.md +9 -6
  315. package/templates/app/docs/errors/CAGE-REG-001.md +13 -12
  316. package/templates/app/docs/errors/CAGE-REG-002.md +2 -1
  317. package/templates/app/docs/errors/CAGE-REG-003.md +1 -1
  318. package/templates/app/docs/errors/CAGE-REL-001.md +14 -14
  319. package/templates/app/docs/errors/CAGE-ROUTE-001.md +7 -6
  320. package/templates/app/docs/errors/CAGE-SCOPE-001.md +1 -1
  321. package/templates/app/docs/errors/CAGE-SCOPE-002.md +2 -2
  322. package/templates/app/docs/errors/CAGE-SECRET-001.md +2 -2
  323. package/templates/app/docs/errors/CAGE-SECRET-002.md +1 -1
  324. package/templates/app/docs/errors/CAGE-SENS-001.md +5 -5
  325. package/templates/app/docs/errors/CAGE-SQL-001.md +1 -1
  326. package/templates/app/docs/errors/CAGE-SQL-002.md +1 -1
  327. package/templates/app/docs/errors/CAGE-STATE-001.md +3 -3
  328. package/templates/app/docs/errors/CAGE-STATE-002.md +1 -1
  329. package/templates/app/docs/errors/CAGE-STATE-003.md +1 -1
  330. package/templates/app/docs/errors/CAGE-STATE-004.md +1 -1
  331. package/templates/app/docs/errors/CAGE-STATE-005.md +3 -2
  332. package/templates/app/docs/errors/CAGE-STATE-010.md +5 -5
  333. package/templates/app/docs/errors/CAGE-STATE-011.md +5 -5
  334. package/templates/app/docs/errors/CAGE-STRUCT-001.md +1 -1
  335. package/templates/app/docs/errors/CAGE-STRUCT-002.md +1 -1
  336. package/templates/app/docs/errors/CAGE-SYNC-001.md +3 -2
  337. package/templates/app/docs/errors/CAGE-TEST-001.md +1 -1
  338. package/templates/app/docs/errors/CAGE-TEST-002.md +50 -0
  339. package/templates/app/docs/errors/CAGE-TEST-003.md +52 -0
  340. package/templates/app/docs/errors/CAGE-UNSAFE-001.md +3 -2
  341. package/templates/app/docs/errors/CAGE-UPGRADE-001.md +2 -2
  342. package/templates/app/docs/errors/CAGE-WORKER-001.md +48 -0
  343. package/templates/app/docs/errors/CAGE-WORKER-002.md +50 -0
  344. package/templates/app/docs/errors/CAGE-WORKER-003.md +48 -0
  345. package/templates/app/docs/errors/README.md +50 -0
  346. package/templates/app/package.json +3 -3
  347. package/templates/app/packages/contracts/package.json +1 -1
  348. package/templates/app/packages/contracts/src/aggregates/dept/index.ts +10 -10
  349. package/templates/app/packages/contracts/src/aggregates/inventory/index.ts +5 -5
  350. package/templates/app/packages/contracts/src/aggregates/inventory-lock/index.ts +4 -4
  351. package/templates/app/packages/contracts/src/aggregates/purchase-order/index.ts +26 -12
  352. package/templates/app/packages/contracts/src/aggregates/role/index.ts +15 -15
  353. package/templates/app/packages/contracts/src/aggregates/user/index.ts +16 -16
  354. package/templates/app/packages/contracts/src/index.ts +7 -1
  355. package/templates/app/packages/contracts/src/queries/index.ts +2 -2
  356. package/templates/app/packages/domain/package.json +5 -4
  357. package/templates/app/packages/domain/src/index.ts +4 -0
  358. package/templates/app/packages/domain/src/integrations/erp.ts +15 -0
  359. package/templates/app/packages/domain/src/integrations/intelligence.ts +20 -0
  360. package/templates/app/packages/domain/src/modules/inventory/index.ts +1 -0
  361. package/templates/app/packages/domain/src/modules/inventory/inventory.ts +12 -0
  362. package/templates/app/packages/domain/src/modules/purchase/approvals.ts +22 -1
  363. package/templates/app/packages/domain/src/modules/purchase/index.ts +1 -0
  364. package/templates/app/packages/domain/src/modules/purchase/intelligence.test.ts +62 -0
  365. package/templates/app/packages/domain/src/modules/purchase/jobs.ts +5 -0
  366. package/templates/app/packages/domain/src/modules/purchase/purchase-order.test.ts +9 -0
  367. package/templates/app/packages/domain/src/modules/purchase/purchase-order.ts +70 -0
  368. package/templates/app/packages/domain/src/modules/purchase/queries.ts +5 -0
  369. package/templates/app/packages/domain/src/workflows/purchase-inventory.ts +6 -0
  370. package/templates/app/packages/domain/src/workflows/purchase-process.ts +42 -0
  371. package/templates/blank/.dockerignore +2 -0
  372. package/templates/blank/.env.example +10 -0
  373. package/templates/blank/AGENTS.md +49 -9
  374. package/templates/blank/README.md +4 -1
  375. package/templates/blank/_gitignore +1 -0
  376. package/templates/blank/apps/api/package.json +1 -1
  377. package/templates/blank/apps/api/src/main.ts +4 -1
  378. package/templates/blank/apps/api/src/worker.ts +2 -0
  379. package/templates/blank/apps/web/index.html +1 -0
  380. package/templates/blank/apps/web/package.json +7 -3
  381. package/templates/blank/apps/web/src/app/app.tsx +80 -18
  382. package/templates/blank/apps/web/src/app/styles/global.css +90 -0
  383. package/templates/blank/apps/web/src/app/use-session.ts +43 -0
  384. package/templates/blank/apps/web/src/app/workspace-layout.tsx +30 -5
  385. package/templates/blank/apps/web/src/main.tsx +5 -0
  386. package/templates/blank/apps/web/src/modules/auth/index.ts +4 -0
  387. package/templates/blank/apps/web/src/modules/auth/pages/login-page.tsx +18 -1
  388. package/templates/blank/apps/web/src/modules/directory/index.ts +7 -0
  389. package/templates/blank/apps/web/src/modules/directory/pages/user-detail-page.tsx +78 -0
  390. package/templates/blank/apps/web/src/modules/directory/pages/users-page.tsx +93 -0
  391. package/templates/blank/apps/web/src/modules/workspace/index.ts +4 -0
  392. package/templates/blank/apps/web/src/modules/workspace/pages/home-page.tsx +17 -0
  393. package/templates/blank/apps/web/src/shared/lib/error-message.ts +11 -0
  394. package/templates/blank/apps/web/src/shared/lib/navigation.ts +21 -0
  395. package/templates/blank/apps/web/src/shared/ui/brand.tsx +10 -2
  396. package/templates/blank/apps/web/src/shared/ui/page-state.css +47 -0
  397. package/templates/blank/apps/web/src/shared/ui/page-state.tsx +76 -0
  398. package/templates/blank/apps/web/src/shared/ui/require-capability.tsx +34 -0
  399. package/templates/blank/apps/web/vite.config.ts +5 -0
  400. package/templates/blank/deploy/nginx.conf +52 -0
  401. package/templates/blank/docs/ai/README.md +21 -0
  402. package/templates/blank/docs/ai/architecture.md +26 -0
  403. package/templates/blank/docs/ai/commenting.md +203 -0
  404. package/templates/blank/docs/ai/diagnostics.md +80 -0
  405. package/templates/blank/docs/ai/files.md +93 -0
  406. package/templates/blank/docs/ai/intelligence.md +348 -0
  407. package/templates/blank/docs/ai/ontology-context.md +59 -4
  408. package/templates/blank/docs/ai/primitives.md +33 -0
  409. package/templates/blank/docs/ai/project-structure.md +8 -3
  410. package/templates/blank/docs/ai/purchase-example.md +16 -2
  411. package/templates/blank/docs/ai/recipes.md +9 -1
  412. package/templates/blank/docs/ai/routing.md +86 -0
  413. package/templates/blank/docs/ai/troubleshooting.md +5 -0
  414. package/templates/blank/docs/ai/workflow.md +24 -11
  415. package/templates/blank/docs/errors/CAGE-AI-001.md +61 -0
  416. package/templates/blank/docs/errors/CAGE-APPR-001.md +7 -6
  417. package/templates/blank/docs/errors/CAGE-APPR-002.md +2 -2
  418. package/templates/blank/docs/errors/CAGE-APPR-003.md +1 -1
  419. package/templates/blank/docs/errors/CAGE-APPR-004.md +4 -4
  420. package/templates/blank/docs/errors/CAGE-APPR-005.md +1 -1
  421. package/templates/blank/docs/errors/CAGE-APPR-006.md +12 -6
  422. package/templates/blank/docs/errors/CAGE-APPR-007.md +3 -3
  423. package/templates/blank/docs/errors/CAGE-APPR-010.md +5 -5
  424. package/templates/blank/docs/errors/CAGE-AUDIT-001.md +53 -0
  425. package/templates/blank/docs/errors/CAGE-AUTH-001.md +1 -1
  426. package/templates/blank/docs/errors/CAGE-BIZ-001.md +51 -0
  427. package/templates/blank/docs/errors/CAGE-BIZ-002.md +49 -0
  428. package/templates/blank/docs/errors/CAGE-BUDGET-001.md +1 -1
  429. package/templates/blank/docs/errors/CAGE-BUDGET-002.md +1 -1
  430. package/templates/blank/docs/errors/CAGE-BUDGET-003.md +1 -1
  431. package/templates/blank/docs/errors/CAGE-CMD-002.md +1 -1
  432. package/templates/blank/docs/errors/CAGE-CMD-003.md +1 -1
  433. package/templates/blank/docs/errors/CAGE-CONN-001.md +3 -3
  434. package/templates/blank/docs/errors/CAGE-CONN-002.md +1 -1
  435. package/templates/blank/docs/errors/CAGE-CONN-003.md +1 -1
  436. package/templates/blank/docs/errors/CAGE-CONN-004.md +1 -1
  437. package/templates/blank/docs/errors/CAGE-CONN-005.md +1 -1
  438. package/templates/blank/docs/errors/CAGE-CONN-006.md +1 -1
  439. package/templates/blank/docs/errors/CAGE-CONN-007.md +6 -6
  440. package/templates/blank/docs/errors/CAGE-CONN-008.md +5 -5
  441. package/templates/blank/docs/errors/CAGE-CONN-009.md +6 -6
  442. package/templates/blank/docs/errors/CAGE-CONN-010.md +6 -6
  443. package/templates/blank/docs/errors/CAGE-CONN-011.md +1 -1
  444. package/templates/blank/docs/errors/CAGE-CONN-012.md +1 -1
  445. package/templates/blank/docs/errors/CAGE-CONN-013.md +49 -0
  446. package/templates/blank/docs/errors/CAGE-CONN-014.md +48 -0
  447. package/templates/blank/docs/errors/CAGE-CONN-015.md +49 -0
  448. package/templates/blank/docs/errors/CAGE-CREATE-001.md +10 -9
  449. package/templates/blank/docs/errors/CAGE-DECL-001.md +2 -2
  450. package/templates/blank/docs/errors/CAGE-DECL-002.md +7 -6
  451. package/templates/blank/docs/errors/CAGE-DECL-003.md +1 -1
  452. package/templates/blank/docs/errors/CAGE-DECL-004.md +1 -1
  453. package/templates/blank/docs/errors/CAGE-DEP-001.md +2 -2
  454. package/templates/blank/docs/errors/CAGE-DEP-002.md +1 -1
  455. package/templates/blank/docs/errors/CAGE-DEP-003.md +2 -2
  456. package/templates/blank/docs/errors/CAGE-DEP-004.md +1 -1
  457. package/templates/blank/docs/errors/CAGE-DEP-005.md +1 -1
  458. package/templates/blank/docs/errors/CAGE-DIAG-001.md +53 -0
  459. package/templates/blank/docs/errors/CAGE-DLQ-001.md +2 -2
  460. package/templates/blank/docs/errors/CAGE-ENV-001.md +4 -4
  461. package/templates/blank/docs/errors/CAGE-ENV-002.md +51 -0
  462. package/templates/blank/docs/errors/CAGE-ENV-003.md +48 -0
  463. package/templates/blank/docs/errors/CAGE-EVENT-001.md +1 -1
  464. package/templates/blank/docs/errors/CAGE-EVENT-002.md +7 -7
  465. package/templates/blank/docs/errors/CAGE-FIELD-001.md +2 -2
  466. package/templates/blank/docs/errors/CAGE-FIELD-002.md +1 -1
  467. package/templates/blank/docs/errors/CAGE-FIELD-003.md +1 -1
  468. package/templates/blank/docs/errors/CAGE-FIELD-004.md +1 -1
  469. package/templates/blank/docs/errors/CAGE-FILE-001.md +56 -0
  470. package/templates/blank/docs/errors/CAGE-GEN-001.md +2 -2
  471. package/templates/blank/docs/errors/CAGE-GEN-002.md +1 -1
  472. package/templates/blank/docs/errors/CAGE-GEN-003.md +53 -0
  473. package/templates/blank/docs/errors/CAGE-GRANT-001.md +3 -3
  474. package/templates/blank/docs/errors/CAGE-GRANT-002.md +2 -2
  475. package/templates/blank/docs/errors/CAGE-GRANT-003.md +7 -6
  476. package/templates/blank/docs/errors/CAGE-IMPACT-001.md +2 -2
  477. package/templates/blank/docs/errors/CAGE-IMPACT-002.md +52 -0
  478. package/templates/blank/docs/errors/CAGE-INIT-001.md +1 -1
  479. package/templates/blank/docs/errors/CAGE-INIT-002.md +1 -1
  480. package/templates/blank/docs/errors/CAGE-INIT-003.md +2 -2
  481. package/templates/blank/docs/errors/CAGE-INIT-004.md +1 -1
  482. package/templates/blank/docs/errors/CAGE-INIT-005.md +52 -0
  483. package/templates/blank/docs/errors/CAGE-JOB-001.md +1 -1
  484. package/templates/blank/docs/errors/CAGE-JOB-002.md +1 -1
  485. package/templates/blank/docs/errors/CAGE-JOB-003.md +1 -1
  486. package/templates/blank/docs/errors/CAGE-LINT-001.md +1 -1
  487. package/templates/blank/docs/errors/CAGE-MIG-001.md +1 -1
  488. package/templates/blank/docs/errors/CAGE-MIG-002.md +1 -1
  489. package/templates/blank/docs/errors/CAGE-MIG-005.md +1 -1
  490. package/templates/blank/docs/errors/CAGE-NAME-001.md +1 -1
  491. package/templates/blank/docs/errors/CAGE-NAME-002.md +1 -1
  492. package/templates/blank/docs/errors/CAGE-NAME-003.md +3 -3
  493. package/templates/blank/docs/errors/CAGE-NAME-004.md +1 -1
  494. package/templates/blank/docs/errors/CAGE-NAME-005.md +1 -1
  495. package/templates/blank/docs/errors/CAGE-NAME-006.md +1 -1
  496. package/templates/blank/docs/errors/CAGE-ORG-001.md +7 -6
  497. package/templates/blank/docs/errors/CAGE-PAIR-002.md +6 -5
  498. package/templates/blank/docs/errors/CAGE-QUERY-001.md +1 -1
  499. package/templates/blank/docs/errors/CAGE-QUERY-002.md +2 -2
  500. package/templates/blank/docs/errors/CAGE-QUERY-003.md +7 -6
  501. package/templates/blank/docs/errors/CAGE-REACT-001.md +3 -3
  502. package/templates/blank/docs/errors/CAGE-REACT-002.md +1 -1
  503. package/templates/blank/docs/errors/CAGE-REACT-003.md +50 -0
  504. package/templates/blank/docs/errors/CAGE-READS-001.md +9 -6
  505. package/templates/blank/docs/errors/CAGE-REG-001.md +13 -12
  506. package/templates/blank/docs/errors/CAGE-REG-002.md +2 -1
  507. package/templates/blank/docs/errors/CAGE-REG-003.md +1 -1
  508. package/templates/blank/docs/errors/CAGE-REL-001.md +14 -14
  509. package/templates/blank/docs/errors/CAGE-ROUTE-001.md +7 -6
  510. package/templates/blank/docs/errors/CAGE-SCOPE-001.md +1 -1
  511. package/templates/blank/docs/errors/CAGE-SCOPE-002.md +2 -2
  512. package/templates/blank/docs/errors/CAGE-SECRET-001.md +2 -2
  513. package/templates/blank/docs/errors/CAGE-SECRET-002.md +1 -1
  514. package/templates/blank/docs/errors/CAGE-SENS-001.md +5 -5
  515. package/templates/blank/docs/errors/CAGE-SQL-001.md +1 -1
  516. package/templates/blank/docs/errors/CAGE-SQL-002.md +1 -1
  517. package/templates/blank/docs/errors/CAGE-STATE-001.md +3 -3
  518. package/templates/blank/docs/errors/CAGE-STATE-002.md +1 -1
  519. package/templates/blank/docs/errors/CAGE-STATE-003.md +1 -1
  520. package/templates/blank/docs/errors/CAGE-STATE-004.md +1 -1
  521. package/templates/blank/docs/errors/CAGE-STATE-005.md +3 -2
  522. package/templates/blank/docs/errors/CAGE-STATE-010.md +5 -5
  523. package/templates/blank/docs/errors/CAGE-STATE-011.md +5 -5
  524. package/templates/blank/docs/errors/CAGE-STRUCT-001.md +1 -1
  525. package/templates/blank/docs/errors/CAGE-STRUCT-002.md +1 -1
  526. package/templates/blank/docs/errors/CAGE-SYNC-001.md +3 -2
  527. package/templates/blank/docs/errors/CAGE-TEST-001.md +1 -1
  528. package/templates/blank/docs/errors/CAGE-TEST-002.md +50 -0
  529. package/templates/blank/docs/errors/CAGE-TEST-003.md +52 -0
  530. package/templates/blank/docs/errors/CAGE-UNSAFE-001.md +3 -2
  531. package/templates/blank/docs/errors/CAGE-UPGRADE-001.md +2 -2
  532. package/templates/blank/docs/errors/CAGE-WORKER-001.md +48 -0
  533. package/templates/blank/docs/errors/CAGE-WORKER-002.md +50 -0
  534. package/templates/blank/docs/errors/CAGE-WORKER-003.md +48 -0
  535. package/templates/blank/docs/errors/README.md +50 -0
  536. package/templates/blank/docs/examples/purchase-admin/apps/api/src/main.ts +5 -1
  537. package/templates/blank/docs/examples/purchase-admin/apps/api/src/worker.ts +3 -0
  538. package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/app.tsx +122 -12
  539. package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/styles/global.css +88 -8
  540. package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/use-session.ts +43 -0
  541. package/templates/blank/docs/examples/purchase-admin/apps/web/src/app/workspace-layout.tsx +58 -19
  542. package/templates/blank/docs/examples/purchase-admin/apps/web/src/main.tsx +5 -0
  543. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/hooks/use-approval-tasks.ts +11 -0
  544. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/index.ts +4 -0
  545. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/approval/pages/approval-tasks-page.tsx +20 -1
  546. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/auth/index.ts +4 -0
  547. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/auth/pages/login-page.tsx +12 -0
  548. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/inventory/index.ts +4 -0
  549. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/inventory/pages/inventory-page.tsx +29 -1
  550. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/approval-history.tsx +32 -0
  551. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/order-attachments.tsx +29 -0
  552. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/purchase-assistant-context.tsx +17 -0
  553. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/purchase-order-form.css +6 -0
  554. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/components/purchase-order-form.tsx +25 -3
  555. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/index.ts +5 -0
  556. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/lib/presentation.ts +20 -0
  557. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-intelligence.css +15 -0
  558. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-order-detail-page.tsx +154 -0
  559. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/purchase/pages/purchase-orders-page.tsx +52 -22
  560. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/workspace/index.ts +5 -0
  561. package/templates/blank/docs/examples/purchase-admin/apps/web/src/modules/workspace/pages/home-page.tsx +44 -0
  562. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/hooks/use-command.ts +13 -0
  563. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/lib/error-message.ts +9 -0
  564. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/lib/navigation.ts +21 -0
  565. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/assistant-markdown.tsx +19 -0
  566. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/assistant-panel.css +134 -0
  567. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/assistant-panel.tsx +200 -0
  568. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/assistant-scope.tsx +72 -0
  569. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/attachment-upload.css +28 -0
  570. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/attachment-upload.tsx +163 -0
  571. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/notice.tsx +7 -0
  572. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/page-heading.tsx +7 -0
  573. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/page-state.css +47 -0
  574. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/page-state.tsx +76 -0
  575. package/templates/blank/docs/examples/purchase-admin/apps/web/src/shared/ui/require-capability.tsx +34 -0
  576. package/templates/blank/docs/examples/purchase-admin/cage.config.json +9 -0
  577. package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/dept/index.ts +10 -10
  578. package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/inventory/index.ts +5 -5
  579. package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/inventory-lock/index.ts +4 -4
  580. package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/purchase-order/index.ts +26 -12
  581. package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/role/index.ts +15 -15
  582. package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/aggregates/user/index.ts +16 -16
  583. package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/index.ts +7 -1
  584. package/templates/blank/docs/examples/purchase-admin/packages/contracts/src/queries/index.ts +2 -2
  585. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/index.ts +4 -0
  586. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/integrations/erp.ts +15 -0
  587. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/integrations/intelligence.ts +20 -0
  588. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/inventory/index.ts +1 -0
  589. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/inventory/inventory.ts +12 -0
  590. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/approvals.ts +22 -1
  591. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/index.ts +1 -0
  592. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/intelligence.test.ts +62 -0
  593. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/jobs.ts +5 -0
  594. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.test.ts +9 -0
  595. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/purchase-order.ts +70 -0
  596. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/modules/purchase/queries.ts +5 -0
  597. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/workflows/purchase-inventory.ts +6 -0
  598. package/templates/blank/docs/examples/purchase-admin/packages/domain/src/workflows/purchase-process.ts +42 -0
  599. package/templates/blank/package.json +3 -3
  600. package/templates/blank/packages/contracts/package.json +1 -1
  601. package/templates/blank/packages/contracts/src/aggregates/dept/index.ts +10 -10
  602. package/templates/blank/packages/contracts/src/aggregates/role/index.ts +15 -15
  603. package/templates/blank/packages/contracts/src/aggregates/user/index.ts +16 -16
  604. package/templates/blank/packages/contracts/src/index.ts +1 -1
  605. package/templates/blank/packages/contracts/src/queries/index.ts +1 -1
  606. package/templates/blank/packages/domain/package.json +5 -4
  607. package/templates/blank/packages/domain/src/index.ts +2 -1
@@ -0,0 +1,163 @@
1
+ /**
2
+ * 受控附件选择与可恢复上传界面,连接客户端分片任务、上传会话查询和父表单的附件 ID。
3
+ * 上传 ready 仅说明字节准备完成,正式绑定到业务记录仍需调用方提交领域命令。
4
+ * 本地校验用于及时反馈,文件策略、归属与下载权限始终由服务端重新检查。
5
+ */
6
+ // 文件上传与业务保存分开:这里只产出已完成的 ID,最终关联仍由调用方的领域命令提交。
7
+ import { useEffect, useRef, useState } from 'react';
8
+ import { useQuery } from '@tanstack/react-query';
9
+ import { client, useFileUpload, type FileUploadTask, type UploadSession } from '@app/contracts';
10
+ import './attachment-upload.css';
11
+
12
+ /**
13
+ * 上传组件契约:父组件持有附件草稿,上传任务状态在本组件内维护。
14
+ */
15
+ interface Props {
16
+ /**
17
+ * 最终绑定附件的完整命令名,供服务端验证上传用途及调用能力。
18
+ */
19
+ command: string;
20
+ /**
21
+ * 该命令目标聚合上的文件字段名,必须匹配生成的字段策略。
22
+ */
23
+ field: string;
24
+ /**
25
+ * 编辑已有记录时的目标 UUID;创建命令尚无记录时省略。
26
+ */
27
+ targetId?: string;
28
+ /**
29
+ * 由生成契约提供的策略:maxBytes 为字节上限,maxCount 缺省按单附件,accept 为允许的 MIME 列表。
30
+ */
31
+ policy: { maxBytes: number; maxCount?: number; accept: readonly string[] };
32
+ /**
33
+ * 父表单当前选择的已准备附件 ID;不是服务端已经保存的关联证明。
34
+ */
35
+ value: string[];
36
+ /**
37
+ * 附件新增或移除时回传完整 ID 列表,父表单决定何时提交。
38
+ */
39
+ onChange: (ids: string[]) => void;
40
+ /**
41
+ * 当任一任务处于上传流程时通知父表单,以便阻止不完整附件的保存。
42
+ */
43
+ onBusyChange: (busy: boolean) => void;
44
+ }
45
+ /**
46
+ * 以 1024 为单位换算文件大小供展示;输入为字节,不参与服务端大小限制判断。
47
+ */
48
+ const size = (bytes: number) => bytes >= 1024 ** 3 ? `${(bytes / 1024 ** 3).toFixed(1)} GB` : bytes >= 1024 ** 2 ? `${(bytes / 1024 ** 2).toFixed(1)} MB` : `${Math.ceil(bytes / 1024)} KB`;
49
+
50
+ /**
51
+ * 按 id 查询可访问的文件元信息,成功后提供受服务端鉴权的下载地址。
52
+ * 加载与不可访问分别提示;不通过存储内部路径绕过下载权限。
53
+ */
54
+ export function FileLink({ id }: { id: string }) {
55
+ const query = useQuery({ queryKey: ['file-info', id], queryFn: async () => {
56
+ const result = await client.fileInfo(id);
57
+ if (!result.ok) throw new Error(result.code);
58
+ return result.data;
59
+ } });
60
+ if (query.isPending) return <span className="muted">正在读取附件…</span>;
61
+ if (query.isError) return <span role="status">附件不可访问</span>;
62
+ return <a href={client.fileDownloadUrl(id)}>{query.data.filename} <small className="muted">{size(query.data.bytes)}</small></a>;
63
+ }
64
+
65
+ /**
66
+ * 先读取匹配 command/field/targetId 的可恢复会话,再创建或继续本地文件任务。
67
+ * 通过 refs 读取最新父表单值及任务列表,卸载时 dispose 已创建任务;取消/删除业务关联不是卸载的隐含效果。
68
+ * 父组件负责保存 value;移除选中项只改变草稿,不直接删除服务端已绑定文件。
69
+ */
70
+ export function AttachmentUpload(props: Props) {
71
+ const { command, field, targetId, policy, value, onChange, onBusyChange } = props;
72
+ const current = useRef(props); current.current = props;
73
+ const [tasks, setTasks] = useState<{ key: number; file: File; task: FileUploadTask }[]>([]);
74
+ const [error, setError] = useState('');
75
+ const sequence = useRef(0), tasksRef = useRef(tasks); tasksRef.current = tasks;
76
+ const [active, setActive] = useState<Record<number, boolean>>({});
77
+ const pending = useQuery({ queryKey: ['file-uploads', command, field, targetId], queryFn: async () => {
78
+ const result = await client.uploadSessions({ command, field, targetId });
79
+ if (!result.ok) throw new Error(result.code);
80
+ return result.data;
81
+ } });
82
+ useEffect(() => { onBusyChange(Object.values(active).some(Boolean)); }, [active, onBusyChange]);
83
+ useEffect(() => () => { for (const entry of tasksRef.current) entry.task.dispose(); }, []);
84
+
85
+ /**
86
+ * 校验选中文件数量、字节大小和 MIME 后构造上传任务;session 可指定待续传的会话。
87
+ * 数量同时计入已选附件与未结束任务,续传时实际文件内容匹配由 SDK/服务端验证。
88
+ */
89
+ function add(files: FileList | null, session?: UploadSession) {
90
+ if (!files?.length) return;
91
+ setError('');
92
+ const maximum = policy.maxCount ?? 1;
93
+ const working = tasks.filter(entry => !['completed', 'cancelled'].includes(entry.task.getSnapshot().status)).length;
94
+ if (value.length + working + files.length > maximum) { setError(`最多选择 ${maximum} 个附件,请先取消多余任务。`); return; }
95
+ for (const file of Array.from(files)) {
96
+ if (file.size < 1 || file.size > policy.maxBytes || !policy.accept.includes(file.type || 'application/octet-stream')) {
97
+ setError(`“${file.name}”的大小或类型不符合要求。`); continue;
98
+ }
99
+ const task = client.upload(file, { command, field, targetId }, { uploadId: session?.id });
100
+ const key = ++sequence.current;
101
+ setActive(previous => ({ ...previous, [key]: true }));
102
+ setTasks(previous => [...previous, { key, file, task }]);
103
+ }
104
+ }
105
+ /**
106
+ * 把已准备的上传 ID 纳入当前父表单草稿;读取最新 props,拒绝重复项及超过数量上限。
107
+ * 名字表示采用完成结果,此函数是事件处理器,不是 React hook。
108
+ */
109
+ function useCompleted(id: string) {
110
+ const { value, onChange, policy } = current.current;
111
+ if (value.includes(id)) return;
112
+ if (value.length >= (policy.maxCount ?? 1)) { setError('附件数量已达到上限,请移除后重试。'); return; }
113
+ onChange([...value, id]);
114
+ }
115
+ const taskIds = new Set(tasks.map(entry => entry.task.getSnapshot().id));
116
+ const recoverable = (pending.data ?? []).filter(session => !value.includes(session.id) && !taskIds.has(session.id));
117
+ return <div className="attachment-upload">
118
+ <div className="attachment-heading"><strong>附件</strong><span className="muted">{value.length} / {policy.maxCount ?? 1}</span></div>
119
+ <label className="attachment-picker">
120
+ <span>+ 选择文件</span>
121
+ <input type="file" multiple={(policy.maxCount ?? 1) > 1} accept={policy.accept.join(',')} disabled={value.length >= (policy.maxCount ?? 1)}
122
+ onChange={event => { add(event.target.files); event.target.value = ''; }} />
123
+ <small>支持暂停与续传 · 单文件最大 {size(policy.maxBytes)}</small>
124
+ </label>
125
+ <p className="muted attachment-hint">上传完成后,保存记录才会正式关联;未完成的上传可以稍后恢复。</p>
126
+ {error && <p role="alert" className="attachment-error">{error}</p>}
127
+ {value.map(id => <div className="attachment-row" key={id}><FileLink id={id} /><button type="button" onClick={() => onChange(value.filter(item => item !== id))}>移除</button></div>)}
128
+ {tasks.map(entry => value.includes(entry.task.getSnapshot().id ?? '') ? null : <UploadRow key={entry.key} file={entry.file} task={entry.task}
129
+ onDone={id => { setActive(previous => ({ ...previous, [entry.key]: false })); useCompleted(id); }} onBusy={busy => setActive(previous => previous[entry.key] === busy ? previous : ({ ...previous, [entry.key]: busy }))} />)}
130
+ {recoverable.length > 0 && <div className="attachment-recovery"><strong>可恢复的上传</strong><p className="muted">继续上传时,请重新选择原文件;已完成部分会先核对内容。</p>
131
+ {recoverable.map(session => <div className="attachment-row" key={session.id}><span>{session.filename} <small className="muted">{size(session.bytes)}</small></span><div className="actions">
132
+ {session.status === 'ready' ? <button type="button" onClick={() => useCompleted(session.id)}>使用附件</button> : <label className="attachment-resume">选择原文件续传<input type="file" onChange={event => { add(event.target.files, session); event.target.value = ''; }} /></label>}
133
+ <button type="button" onClick={async () => { const result = await client.cancelUpload(session.id); if (result.ok) void pending.refetch(); else setError('取消失败,请重试。'); }}>取消</button>
134
+ </div></div>)}
135
+ </div>}
136
+ {pending.isError && <p role="status" className="muted">暂时无法查询可恢复任务。<button type="button" onClick={() => void pending.refetch()}>重试</button></p>}
137
+ </div>;
138
+ }
139
+
140
+ /**
141
+ * 订阅单个 FileUploadTask,挂载后启动,向父组件报告 busy 与完成 ID。
142
+ * 进度使用 confirmedBytes;暂停/继续/取消交给 SDK,失败从订阅快照显示。
143
+ * start/cancel 的 Promise 失败在这里不另行抛到页面,错误可见性依赖任务状态;资源释放由外层统一处理。
144
+ */
145
+ function UploadRow({ file, task, onDone, onBusy }: { file: File; task: FileUploadTask; onDone: (id: string) => void; onBusy: (busy: boolean) => void }) {
146
+ const state = useFileUpload(task);
147
+ const callbacks = useRef({ onDone, onBusy }); callbacks.current = { onDone, onBusy };
148
+ const busy = ['initializing', 'verifying', 'uploading', 'completing'].includes(state.status);
149
+ const start = () => { void task.start().then(session => callbacks.current.onDone(session.id)).catch(() => {}); };
150
+ useEffect(() => { start(); }, [task]);
151
+ useEffect(() => { callbacks.current.onBusy(busy); }, [busy]);
152
+ const labels = { idle: '等待上传', initializing: '准备上传', verifying: '核对已上传内容', uploading: '上传中', paused: '已暂停', completing: '校验与合并中', completed: '上传完成', failed: '上传失败,可重试', cancelled: '已取消' };
153
+ if (state.status === 'cancelled') return null;
154
+ return <div className="attachment-task"><div className="attachment-row"><span>{file.name}</span><div className="actions">
155
+ {busy && state.status !== 'completing' && <button type="button" onClick={() => task.pause()}>暂停</button>}
156
+ {['paused', 'failed', 'completed'].includes(state.status) && <button type="button" onClick={state.status === 'completed' && state.id ? () => onDone(state.id!) : start}>{state.status === 'completed' ? '使用附件' : '继续'}</button>}
157
+ <button type="button" onClick={() => { void task.cancel().catch(() => {}); }}>取消</button>
158
+ </div></div>
159
+ <progress aria-label={`${file.name} 上传进度`} max={state.totalBytes} value={state.confirmedBytes} />
160
+ <div className="attachment-progress" role="status"><span>{labels[state.status]}</span><span>{size(state.confirmedBytes)} / {size(state.totalBytes)}{state.bytesPerSecond > 0 ? ` · ${size(state.bytesPerSecond)}/s` : ''}</span></div>
161
+ {state.error && <p className="attachment-error">{({ NETWORK: '网络中断,已上传内容会保留。', TIMEOUT: '请求超时,可继续重试。', FORBIDDEN: '权限不足,请联系管理员。', UNAUTHENTICATED: '登录已失效,请重新登录。', NOT_FOUND: '上传会话或业务记录不可访问。', INVALID_INPUT: '文件不符合要求,请重新选择。', CONFLICT: '上传状态有变化,请稍后重试。', RATE_LIMITED: '上传配额或请求次数已达上限,请稍后重试。' } as Record<string, string>)[state.error] ?? state.error}</p>}
162
+ </div>;
163
+ }
@@ -1,6 +1,13 @@
1
+ /**
2
+ * 业务操作结果的可关闭提示,内容和关闭状态由页面或 useCommand 持有。
3
+ */
1
4
  import type { ReactNode } from 'react';
2
5
 
3
6
  /** 通用提示容器只负责呈现;提示文案和关闭后的业务行为由调用页面决定。 */
7
+ /**
8
+ * 呈现 children 消息并在关闭按钮点击时调用 onClose。
9
+ * 仅关闭提示,不回滚已经提交的命令,也不取消正在运行的任务。
10
+ */
4
11
  export function Notice({ children, onClose }: { children: ReactNode; onClose: () => void }) {
5
12
  return (
6
13
  <div role="status" className="notice">
@@ -1,6 +1,13 @@
1
+ /**
2
+ * 业务页公共标题区,统一标题、说明与右侧操作的布局,不包含查询和权限逻辑。
3
+ */
1
4
  import type { ReactNode } from 'react';
2
5
 
3
6
  /** 统一页面标题的排版;不读取路由或业务状态,避免共享 UI 反向依赖某项业务。 */
7
+ /**
8
+ * 展示 eyebrow 分类文字、title 主标题、description 说明和 children 操作区。
9
+ * 操作按钮及其 can 检查由使用页面提供;此组件不会替调用方判断权限。
10
+ */
4
11
  export function PageHeading({
5
12
  eyebrow,
6
13
  title,
@@ -0,0 +1,47 @@
1
+ /**
2
+ * 共享页面状态卡与返回链接的样式,采购和空白模板均由同步流程取得同一版本。
3
+ * 颜色变量由应用主题提供;这里只排版标题、说明和操作,具体错误分类由 PageState/QueryFailure 决定。
4
+ */
5
+ /* 与两套工作台共享的纸面状态卡;状态清楚可读,链接保持可聚焦和可复制。 */
6
+ .route-state {
7
+ margin: 32px 0;
8
+ padding: 68px 36px;
9
+ border: 1px solid var(--line);
10
+ border-radius: 7px;
11
+ background: #fdfefb;
12
+ text-align: center;
13
+ }
14
+ .route-state-mark {
15
+ display: block;
16
+ margin-bottom: 20px;
17
+ color: #7d9866;
18
+ font:
19
+ italic 42px Georgia,
20
+ serif;
21
+ }
22
+ .route-state h1 {
23
+ font-size: 26px;
24
+ letter-spacing: -0.5px;
25
+ }
26
+ .route-state p {
27
+ margin: 16px 0 26px;
28
+ color: var(--muted);
29
+ font-size: 13px;
30
+ line-height: 1.9;
31
+ }
32
+ .route-link {
33
+ display: inline-flex;
34
+ align-items: center;
35
+ padding: 10px 16px;
36
+ border: 1px solid var(--line);
37
+ border-radius: 4px;
38
+ background: #f1f5eb;
39
+ color: var(--green);
40
+ font-size: 12px;
41
+ }
42
+ .route-link:hover {
43
+ background: #e4ecd9;
44
+ }
45
+ .route-state > button {
46
+ margin-left: 12px;
47
+ }
@@ -0,0 +1,76 @@
1
+ /**
2
+ * 供列表、详情和路由守卫复用的页面级状态组件。
3
+ * 将服务端可确认的错误与可恢复操作对应,保留返回链接;不泄露范围外记录是否真实存在。
4
+ */
5
+ import type { ReactNode } from 'react';
6
+ import { Link } from 'react-router-dom';
7
+ import { CageQueryError } from '@app/contracts';
8
+ import './page-state.css';
9
+
10
+ /** 页面级状态保留导航入口,详情直达失败时也能回到列表,而不是出现空白页。 */
11
+ /**
12
+ * 渲染标题、说明、返回链接和可选附加操作。
13
+ * props.title/description 为调用方提供的当前状态;to 是业务返回地址,label 缺省为返回列表。
14
+ * children 可放重试按钮;组件只呈现内容,不自行读取数据或推断错误。
15
+ */
16
+ export function PageState({
17
+ title,
18
+ description,
19
+ to,
20
+ label = '返回列表',
21
+ children,
22
+ }: {
23
+ title: string;
24
+ description: string;
25
+ to: string;
26
+ label?: string;
27
+ children?: ReactNode;
28
+ }) {
29
+ return (
30
+ <section className="route-state" aria-labelledby="route-state-title">
31
+ <span className="route-state-mark" aria-hidden="true">
32
+ ↗
33
+ </span>
34
+ <h1 id="route-state-title">{title}</h1>
35
+ <p>{description}</p>
36
+ <Link className="route-link" to={to}>
37
+ {label}
38
+ </Link>
39
+ {children}
40
+ </section>
41
+ );
42
+ }
43
+
44
+ /** 按服务端错误区分权限、范围/不存在和服务故障;不从 URL 推断记录是否存在。 */
45
+ /**
46
+ * 将查询异常映射为页面状态。CageQueryError 的 FORBIDDEN 与 NOT_FOUND 不展示重试按钮,其他错误允许调用 retry。
47
+ * props.error 为实际查询异常;to/label 决定退出路径,retry 由页面绑定 refetch。
48
+ * NOT_FOUND 同时涵盖记录不存在与不在可访问范围,不能据此向用户确认数据库中有无此记录。
49
+ */
50
+ export function QueryFailure({
51
+ error,
52
+ to,
53
+ label,
54
+ retry,
55
+ }: {
56
+ error: Error;
57
+ to: string;
58
+ label?: string;
59
+ retry: () => void;
60
+ }) {
61
+ const code = error instanceof CageQueryError ? error.result.code : undefined;
62
+ const title = code === 'FORBIDDEN' ? '暂无访问权限' : code === 'NOT_FOUND' ? '记录不可用' : '暂时无法读取';
63
+ const description =
64
+ code === 'FORBIDDEN'
65
+ ? '当前账号没有访问此页面所需的权限,请联系管理员。'
66
+ : code === 'NOT_FOUND'
67
+ ? '记录不存在,或不在你当前可访问的数据范围内。'
68
+ : code === 'UNAUTHENTICATED'
69
+ ? '登录已过期,请重新登录后继续。'
70
+ : '请检查网络或稍后重新尝试。';
71
+ return (
72
+ <PageState title={title} description={description} to={to} label={label}>
73
+ {code !== 'FORBIDDEN' && code !== 'NOT_FOUND' && <button onClick={retry}>重新读取</button>}
74
+ </PageState>
75
+ );
76
+ }
@@ -0,0 +1,34 @@
1
+ /**
2
+ * 业务路由的界面能力守卫,根据当前 SDK 能力快照决定渲染页面还是无权限提示。
3
+ * 服务端对请求独立授权;缓存中的能力只影响界面入口,不增加数据可见范围。
4
+ */
5
+ import type { ReactNode } from 'react';
6
+ import { useCan } from '@app/contracts';
7
+ import { PageState } from './page-state';
8
+
9
+ /** 直接输入 URL 也给出权限提示;真正的授权和数据范围仍由后端接口强制执行。 */
10
+ /**
11
+ * 检查 capability 对应的页面入口权限,允许时渲染 children,否则提供回到 home 的链接。
12
+ * 需放在已恢复会话的路由内,避免把尚未加载的能力列表当作最终账号权限。
13
+ */
14
+ export function RequireCapability({
15
+ capability,
16
+ children,
17
+ home,
18
+ }: {
19
+ capability: Parameters<typeof useCan>[0];
20
+ children: ReactNode;
21
+ home: string;
22
+ }) {
23
+ const allowed = useCan(capability);
24
+ return allowed ? (
25
+ children
26
+ ) : (
27
+ <PageState
28
+ title="暂无访问权限"
29
+ description="当前账号没有访问此页面所需的权限,请联系管理员。"
30
+ to={home}
31
+ label="返回工作台"
32
+ />
33
+ );
34
+ }
@@ -1,3 +1,8 @@
1
+ /**
2
+ * 采购 Web 的 Vite 配置:选定严格端口并将 API、认证、附件等请求代理到本地后端。
3
+ * 对 React 及查询库去重,避免 workspace 中出现多个 hooks 上下文。
4
+ * 此处代理只服务开发环境,生产部署的 SPA 回退与接口转发由部署配置负责。
5
+ */
1
6
  import { defineConfig } from 'vite';
2
7
 
3
8
  // 浏览器请求保持同源,让 cookie 会话和 CSRF 验证与生产环境使用同一种方式。
@@ -6,6 +6,8 @@
6
6
  "graph": {
7
7
  "title": "采购业务图谱",
8
8
  "labels": {
9
+ "module:purchase": "采购管理",
10
+ "module:inventory": "库存管理",
9
11
  "aggregate:PurchaseOrder": "采购单",
10
12
  "aggregate:Inventory": "库存台账",
11
13
  "aggregate:InventoryLock": "库存锁",
@@ -13,6 +15,13 @@
13
15
  "aggregate:User": "用户",
14
16
  "aggregate:Role": "角色",
15
17
  "command:PurchaseOrder.create": "新建采购单",
18
+ "command:PurchaseOrder.explainPurpose": "补充采购用途",
19
+ "command:PurchaseOrder.returnForChanges": "退回修改",
20
+ "field:PurchaseOrder.reason": "采购用途",
21
+ "capability:Sys.ai.use": "使用智能助理",
22
+ "capability:Sys.ai.models.manage": "管理 AI 模型",
23
+ "command:PurchaseOrder.setAttachments": "修改采购附件",
24
+ "field:PurchaseOrder.attachments": "采购附件",
16
25
  "command:PurchaseOrder.submit": "提交审批",
17
26
  "command:PurchaseOrder.approve": "通过审批",
18
27
  "command:PurchaseOrder.reject": "驳回审批",
@@ -0,0 +1,12 @@
1
+ -- 迁移草稿:由 cage gen 生成,人审后提交;已发布的迁移不要修改
2
+ -- 破坏性变更按「先扩展再收缩」分多次发布
3
+
4
+ -- 新列 PurchaseOrder.attachments(必填):先加可空列,回填后再加 NOT NULL
5
+ ALTER TABLE "core"."purchase_order" ADD COLUMN IF NOT EXISTS "attachments" jsonb;
6
+ -- 已有采购单没有附件;审查后的前向迁移只填充新列,保留所有既有业务字段。
7
+ UPDATE "core"."purchase_order" SET "attachments" = '[]'::jsonb WHERE "attachments" IS NULL;
8
+ ALTER TABLE "core"."purchase_order" ALTER COLUMN "attachments" SET NOT NULL;
9
+
10
+ /* cage:schema
11
+ {"version":1,"tables":[{"schema":"core","name":"inventory","aggregate":"Inventory","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"sku","type":"text","notNull":true,"unique":true},{"name":"available","type":"integer","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","deleted"],"indexes":[{"name":"inventory_status_idx","columns":["tenant_id","status"]}]},{"schema":"core","name":"inventory_lock","aggregate":"InventoryLock","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"order_id","type":"uuid","notNull":true},{"name":"items","type":"jsonb","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["held","released"],"indexes":[{"name":"inventory_lock_status_idx","columns":["tenant_id","status"]}]},{"schema":"sys","name":"dept","aggregate":"Dept","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"name","type":"text","notNull":true},{"name":"code","type":"text","notNull":false},{"name":"parent_id","type":"uuid","notNull":false,"references":"sys.dept"},{"name":"ancestors","type":"text","notNull":true},{"name":"leader_id","type":"uuid","notNull":false},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","archived"],"indexes":[{"name":"dept_status_idx","columns":["tenant_id","status"]},{"name":"dept_parent_id_idx","columns":["parent_id"]}]},{"schema":"sys","name":"user","aggregate":"User","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"username","type":"text","notNull":true,"unique":true},{"name":"name","type":"text","notNull":true},{"name":"dept_id","type":"uuid","notNull":true,"references":"sys.dept"},{"name":"role_ids","type":"jsonb","notNull":true},{"name":"phone","type":"text","notNull":false},{"name":"email","type":"text","notNull":false},{"name":"sso_subject","type":"text","notNull":false},{"name":"password_hash","type":"text","notNull":false},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","disabled"],"indexes":[{"name":"user_tenant_dept_id_idx","columns":["tenant_id","dept_id"]},{"name":"user_status_idx","columns":["tenant_id","status"]}]},{"schema":"core","name":"purchase_order","aggregate":"PurchaseOrder","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"dept_id","type":"uuid","notNull":true,"references":"sys.dept"},{"name":"created_by","type":"uuid","notNull":true,"references":"sys.user"},{"name":"supplier_phone","type":"text","notNull":false},{"name":"amount","type":"numeric(18,2)","notNull":true},{"name":"items","type":"jsonb","notNull":true},{"name":"erp_no","type":"text","notNull":false},{"name":"attachments","type":"jsonb","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["draft","pending_approval","approved","received","rejected","cancelled"],"indexes":[{"name":"purchase_order_tenant_dept_id_idx","columns":["tenant_id","dept_id"]},{"name":"purchase_order_tenant_created_by_idx","columns":["tenant_id","created_by"]},{"name":"purchase_order_status_idx","columns":["tenant_id","status"]}]},{"schema":"sys","name":"role","aggregate":"Role","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"code","type":"text","notNull":true,"unique":true},{"name":"name","type":"text","notNull":true},{"name":"capabilities","type":"jsonb","notNull":true},{"name":"scopes","type":"jsonb","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","archived"],"indexes":[{"name":"role_status_idx","columns":["tenant_id","status"]}]}]}
12
+ */
@@ -0,0 +1,8 @@
1
+ -- 迁移草稿:由 cage gen 生成,人审后提交;已发布的迁移不要修改
2
+ -- 破坏性变更按「先扩展再收缩」分多次发布
3
+
4
+ ALTER TABLE "core"."purchase_order" ADD COLUMN IF NOT EXISTS "reason" text;
5
+
6
+ /* cage:schema
7
+ {"version":1,"tables":[{"schema":"core","name":"inventory","aggregate":"Inventory","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"sku","type":"text","notNull":true,"unique":true},{"name":"available","type":"integer","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","deleted"],"indexes":[{"name":"inventory_status_idx","columns":["tenant_id","status"]}]},{"schema":"core","name":"inventory_lock","aggregate":"InventoryLock","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"order_id","type":"uuid","notNull":true},{"name":"items","type":"jsonb","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["held","released"],"indexes":[{"name":"inventory_lock_status_idx","columns":["tenant_id","status"]}]},{"schema":"sys","name":"dept","aggregate":"Dept","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"name","type":"text","notNull":true},{"name":"code","type":"text","notNull":false},{"name":"parent_id","type":"uuid","notNull":false,"references":"sys.dept"},{"name":"ancestors","type":"text","notNull":true},{"name":"leader_id","type":"uuid","notNull":false},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","archived"],"indexes":[{"name":"dept_status_idx","columns":["tenant_id","status"]},{"name":"dept_parent_id_idx","columns":["parent_id"]}]},{"schema":"sys","name":"user","aggregate":"User","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"username","type":"text","notNull":true,"unique":true},{"name":"name","type":"text","notNull":true},{"name":"dept_id","type":"uuid","notNull":true,"references":"sys.dept"},{"name":"role_ids","type":"jsonb","notNull":true},{"name":"phone","type":"text","notNull":false},{"name":"email","type":"text","notNull":false},{"name":"sso_subject","type":"text","notNull":false},{"name":"password_hash","type":"text","notNull":false},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","disabled"],"indexes":[{"name":"user_tenant_dept_id_idx","columns":["tenant_id","dept_id"]},{"name":"user_status_idx","columns":["tenant_id","status"]}]},{"schema":"core","name":"purchase_order","aggregate":"PurchaseOrder","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"dept_id","type":"uuid","notNull":true,"references":"sys.dept"},{"name":"created_by","type":"uuid","notNull":true,"references":"sys.user"},{"name":"supplier_phone","type":"text","notNull":false},{"name":"amount","type":"numeric(18,2)","notNull":true},{"name":"reason","type":"text","notNull":false},{"name":"items","type":"jsonb","notNull":true},{"name":"erp_no","type":"text","notNull":false},{"name":"attachments","type":"jsonb","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["draft","pending_approval","approved","received","rejected","cancelled"],"indexes":[{"name":"purchase_order_tenant_dept_id_idx","columns":["tenant_id","dept_id"]},{"name":"purchase_order_tenant_created_by_idx","columns":["tenant_id","created_by"]},{"name":"purchase_order_status_idx","columns":["tenant_id","status"]}]},{"schema":"sys","name":"role","aggregate":"Role","columns":[{"name":"id","type":"uuid","notNull":true},{"name":"tenant_id","type":"uuid","notNull":true},{"name":"code","type":"text","notNull":true,"unique":true},{"name":"name","type":"text","notNull":true},{"name":"capabilities","type":"jsonb","notNull":true},{"name":"scopes","type":"jsonb","notNull":true},{"name":"status","type":"text","notNull":true},{"name":"version","type":"integer","notNull":true},{"name":"created_at","type":"timestamptz","notNull":true},{"name":"updated_at","type":"timestamptz","notNull":true}],"statusValues":["active","archived"],"indexes":[{"name":"role_status_idx","columns":["tenant_id","status"]}]}]}
8
+ */
@@ -0,0 +1,52 @@
1
+ # 示例:Nginx 与 Cage API 在同一主机,API 监听 3000。
2
+ # 将 root 换成实际构建产物目录;容器部署时将 proxy_pass 换成 API 服务地址。
3
+ server {
4
+ listen 80;
5
+ server_name _;
6
+ root /srv/cage/web;
7
+ index index.html;
8
+
9
+ # 单次请求只承载一个分片;流式转发避免代理先把完整分片落盘。
10
+ location ~ ^/api/sys/uploads/[^/]+/parts/[0-9]+$ {
11
+ client_max_body_size 16m;
12
+ proxy_request_buffering off;
13
+ proxy_http_version 1.1;
14
+ proxy_read_timeout 1800s;
15
+ proxy_pass http://127.0.0.1:3000;
16
+ proxy_set_header Host $host;
17
+ proxy_set_header X-Forwarded-Proto $scheme;
18
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
19
+ }
20
+
21
+ # AI 事件必须逐段送达浏览器,不能由代理压缩或聚齐完整回复再交付。
22
+ location ~ ^/api/ai/(runs/[^/]+/events|admin/personality-preview)$ {
23
+ proxy_pass http://127.0.0.1:3000;
24
+ proxy_http_version 1.1;
25
+ proxy_buffering off;
26
+ proxy_cache off;
27
+ gzip off;
28
+ proxy_read_timeout 75s;
29
+ proxy_set_header Host $host;
30
+ proxy_set_header X-Forwarded-Proto $scheme;
31
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
32
+ }
33
+
34
+ # 服务端拥有的入口保留原 URI;包含 /admin 的页面、脚本和样式。
35
+ location ~ ^/(api|auth|hooks|admin)(/|$) {
36
+ proxy_pass http://127.0.0.1:3000;
37
+ proxy_set_header Host $host;
38
+ proxy_set_header X-Forwarded-Proto $scheme;
39
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
40
+ }
41
+ location = /healthz {
42
+ proxy_pass http://127.0.0.1:3000;
43
+ }
44
+ # 丢失的构建资源直接 404,防止浏览器把 index.html 当作 JS/CSS 解析。
45
+ location ^~ /assets/ {
46
+ try_files $uri =404;
47
+ }
48
+ # 业务详情深链接交给 React Router,刷新后仍能进入同一页面。
49
+ location / {
50
+ try_files $uri $uri/ /index.html;
51
+ }
52
+ }
@@ -25,16 +25,27 @@
25
25
  | 1 | [架构与执行模型](architecture.md) | 聚合是什么?为什么写入必须走命令?数据库由谁连接? |
26
26
  | 2 | [工程目录与代码组织](project-structure.md) | 新业务放哪里?页面如何拆分?哪些依赖会被门禁阻止? |
27
27
  | 3 | [原语与最小业务](primitives.md) | 如何新增字段、CRUD 命令、读依赖、事件和查询? |
28
+ | 编码必读 | [详细中文注释规范](commenting.md) | 如何说明文件职责、API、字段、关键流程、权限/事务与失败边界,并同步维护注释? |
28
29
  | 开发前 | [本体查询与 AI 上下文](ontology-context.md) | 按需求查哪些代码?为什么关联?候选、证据与子图边界怎么判断? |
30
+ | 开发与排错 | [能力发现与状态诊断](diagnostics.md) | 公开 API 有什么?注册/契约/进程哪里过期?如何查执行时间线和授权拒绝? |
29
31
  | 按需 | [对象关系](relations.md) | 如何在 fields 中声明一对一、一对多、多对多,修改关联并分页读取? |
32
+ | 前端必读 | [业务路由与浏览器验收](routing.md) | 列表/详情如何获得独立 URL?登录回跳、404、/admin 和部署回退怎样验收? |
33
+ | 原生 AI | [智能助理与 AI 审批](intelligence.md) | 如何启用后端、配置三态审核、生成工具、传入页面快照和接入 SDK? |
30
34
  | 4 | [标准采购范例导读](purchase-example.md) | 一次提交如何进入审批、锁库存、推 ERP、释放资源? |
31
35
  | 5 | [开发步骤](workflow.md) | 改哪个文件?运行哪些命令?检查结果在哪里? |
32
36
  | 6 | [业务任务配方](recipes.md) | 如何从采购演示换成自己的业务?常见需求如何交给 AI? |
33
37
  | 7 | [业务页导入导出](transfers.md) | 如何提交任务、准备 CSV、查询状态、下载和展示报告? |
34
38
  | 8 | [排错方法](troubleshooting.md)、[错误码册](../errors/README.md) | 如何区分配置、权限、业务失败和异步死信? |
39
+ | 按需 | [文件字段与断点续传](files.md) | 私有上传、分片恢复、命令绑定、下载授权和存储部署 |
35
40
 
36
41
  第一次先读架构、目录规范和原语,再沿范例读代码。接到业务需求后先运行 `pnpm exec cage context "需求关键词"`,确认候选入口,再按源码位置阅读对应实现。
37
42
 
43
+ 框架源码与脚手架应用都要求详细中文注释。新建或修改代码时按[注释规范](commenting.md)同步说明文件职责与调用链、导出 API/类型字段、组件/hooks 和关键流程的“做什么、为什么、怎么做、边界”;修改既有行为时连同相关上下游说明一起更新。注释属于交付内容,不能用“代码自解释”免除,也不能以逐行翻译或注释字数代替质量。
44
+
45
+ 不确定框架支持什么时先用 `pnpm exec cage capabilities 关键词 --format json` 查看当前版本公开签名;功能未生效时用 `pnpm exec cage doctor` 区分注册、生成、构建、迁移、授权和实际进程。未检查项为 `unknown`,不能作为验证通过。
46
+
47
+ 遇到 AI 审批、业务助理、随页助理、模型管理、性格或 Responses 需求,先读[原生智能化指南](intelligence.md)。这些是框架可选能力:后端循环、工具、会话与偏好记忆、HTTP 和 SDK 由框架提供;应用配置声明、策略、授权、页面快照及界面。入口不要求每次加载整份本体或全部指南,按本次任务读取相关章节即可。
48
+
38
49
  ## 按任务找入口
39
50
 
40
51
  | 用户需求 | 优先修改 | 需要一起考虑 |
@@ -42,11 +53,16 @@
42
53
  | 增加业务对象、字段 | `packages/domain/src/modules/<业务>` | 模块公开入口、范围字段、不变量、契约、迁移、角色授权、图谱中文名 |
43
54
  | 增加对象关联 | `fields` 的 ref / hasMany / manyToMany;见[关系指南](relations.md) | 归属命令、双侧权限与范围、目标分页、迁移;纯关联不手写中间表聚合 |
44
55
  | 增加操作按钮 | 领域命令 + `apps/web/src/modules/<业务>` | 状态迁移、调用身份、错误展示、提交幂等键 |
56
+ | 增加列表、详情或新页面 | 模块 pages + app 路由表 | URL、查询条件、深链接、刷新、前进后退、登录回跳;见[路由指南](routing.md) |
45
57
  | 增加报表 | 所属领域模块 `queries.ts` | `reads`、`safe.*`、参数化、调用人 read 权限 |
46
58
  | 修改审批顺序/条件/选人 | 所属领域模块 `approvals.ts` | `routable`、角色/负责人数据、结论命令约束 |
59
+ | AI 预审、退回修改、转人工 | 现有 `step` 的 `kind: 'ai'` + `approval` 绑定 | 策略、取证字段/查询、`automatedBy` 角色、结论命令及 `handoff`;见[智能化指南](intelligence.md) |
60
+ | 业务助理、随页悬浮窗 | domain 导出 `ai()`;页面接 `useAssistant` 与 `useAiPageContext` | 业务权限、页面快照、流式 Markdown、写入确认;界面由应用实现 |
61
+ | 模型动态配置、Responses、性格 | `managedModel` 与框架 Admin 页面 | 部署加密密钥、管理权限、实际网关能力、性格仅影响表达 |
47
62
  | 取消后释放占用 | `workflows/<相关业务>.ts` + 领域事件/命令 | 所有离开持有状态的路径、同事务/异步选择 |
48
63
  | 接 ERP 或其他系统 | `integrations/<系统名>.ts` | schema、密钥名、白名单、幂等、回调验签、重试 |
49
64
  | 业务页导入/导出 | 业务前端调用已有接口 | 任务状态、worker、权限、导入目标命令;不固定到系统管理页 |
65
+ | 文件上传/业务附件 | file/files 字段 + 用户命令 + 客户端上传任务 | 断点续传、当前权限、命令绑定、私有存储及清理;见[文件指南](files.md) |
50
66
  | 新业务没出现在图或 API | domain 统一入口 | 顶层 `export const`、导出名唯一、入口 re-export |
51
67
  | 框架无法表达业务 | 先核对公开导出与范例 | 说明能力缺口;不要编造 API 或把业务分支塞进内核 |
52
68
 
@@ -68,9 +84,14 @@
68
84
  | 聚合/命令、能力与范围、掩码、审计、outbox、worker | handler 不做数据库/网络 IO;生产角色不会自动拿到新能力 |
69
85
  | fields 中的 ref / hasMany / manyToMany | hasMany 为只读反查;manyToMany 自动中间表并随主对象事务写入,公开读取独立分页;双向集合不会自动互为镜像 |
70
86
  | 本体抽取、中文检索/节点/路径/AI 上下文、图规则、图谱、生成、影响分析与门禁 | 上下文注明证据、源码和覆盖边界;名称与测试匹配是候选线索,不能证明任意 JS 正确;跳过用例仍需补足 |
87
+ | 有界集合 reactionEach、显式步骤 order | 稳定唯一键排序后串行调用目标命令;同事务整体回滚,异步链按 outbox 恢复;不是并行写表接口 |
88
+ | capabilities、doctor、timeline、explain、snapshot | 实际运行诊断需要受控会话与权限;未取得的证据为 unknown,授权解释不执行 handler |
71
89
  | 代码审批路由与适配接口 | 顺序步骤、条件步骤、步骤内任一/全部审批;没有通用可视化流程设计器、任意 JS 选人、转签/加签 API |
90
+ | 原生 AI 审批与领域助理 | 现有 `step` 支持 `kind: 'ai'`;三态审核、自动工具、会话及偏好记忆可复用;需显式启用、授权并做模型质量验收 |
91
+ | Admin 模型与性格、助理 SDK | 动态模型连接和性格管理;Chat Completions / Responses 兼容性以实际服务为准;SDK 提供流式状态与页面快照,悬浮窗和 Markdown 由应用实现 |
72
92
  | 连接器 fake、出站、入站验签、同步记录 | fake 不等于真实 ERP 联调,也不等于完整请求录制/回放平台 |
73
93
  | 异步 CSV 导入/导出接口与客户端任务查询 | 界面由业务项目决定;导出结果不是可直接回导的命令输入模板 |
94
+ | 私有文件字段、流式分片上传、暂停与续传、范围下载 | 默认本地存储,提供对象存储适配接口;尚无内置 S3/OSS、签名直传、病毒扫描及文件共享 |
74
95
  | 组织模型、系统管理界面 | 暂无移动部门(改上级)的完整支持、菜单配置页 |
75
96
  | 库存锁采购范例 | 演示资源生命周期;不是仓库级并发库存扣减、退货或 ERP 撤销补偿系统 |
76
97
 
@@ -69,6 +69,14 @@ worker → 消费 outbox → 异步反应 / 审批 / 连接器
69
69
 
70
70
  资源标签 `acquires: 'inventory.lock'`、`releases: 'inventory.lock'` 为图规则提供证据。它们不会自动加锁或释放,真正效果来自 `acquireLock/releaseLock` 命令。
71
71
 
72
+ 有界批量步骤可用 `reactionEach`:声明目标命令、最大项数、唯一排序键和映射,按稳定顺序串行调用。`order` 表达同一事件的步骤依赖,同事务链仍整体回滚。具体声明见[原语指南](primitives.md#有界集合与步骤顺序)。
73
+
74
+ 出站分成外部执行与短事务回写两个阶段。框架先领取本地执行租约,用 `${tenantId}:${declaredKey}` 作为远端幂等键,可靠保存回执后再在短事务调用成功命令;回写失败可复用已保存回执。远端必须实际支持幂等,框架不能承诺任意外部系统恰好一次。不可安全重试的写入结果未知时隔离并要求核对,不能把超时当作远端未执行。
75
+
76
+ 当前没有通用的远端回执确认或解除 `unknown` 的管理入口,普通死信重放不会解除未知状态。此类操作要按连接器实际协议制定受控恢复或业务补偿;不能发明恢复 API,也不能直接 SQL 改执行状态后重发。
77
+
78
+ 首阶段按单机、API 与 worker 同版本升级部署。worker 启动校验必要引擎,发布消费者清单、注册指纹和心跳;不认识事件或缺少必要消费者时不会静默完成。停止时先停新任务、取消可取消 IO,再有界等待在途工作;定时任务同时使用本地运行标记与数据库互斥锁防止跨周期/实例重叠。连接器限流和认证队列均为单进程预算,多实例共享限流另需设计。
79
+
72
80
  ## 权限是几层判断
73
81
 
74
82
  | 层 | 例子 | 说明 |
@@ -84,6 +92,23 @@ worker → 消费 outbox → 异步反应 / 审批 / 连接器
84
92
 
85
93
  `useCan()`、`can()` 只决定按钮呈现;前端隐藏按钮不能代替后端授权。新增命令不会自动授给已有生产角色,角色分配和额度调整也受防提权约束。
86
94
 
95
+ 授权通知监听异常时不再复用缓存,每次重新读取授权;重新订阅后清空缓存,并阻止旧请求回填旧上下文。`/readyz` 反映数据库和授权监听健康,`/healthz` 仅表示存活。诊断入口受 `Sys.ops.read` 及相关对象 read/范围控制,`explain` 解释调用身份、能力、范围、状态与硬约束,不能预测 handler 的业务结果;新建对象的最终范围无法提前确定时返回 unknown。
96
+
97
+ ## 审计保证
98
+
99
+ | 入口 | 保证与失败行为 |
100
+ | --- | --- |
101
+ | 命令成功 | 与业务变更、版本和 outbox 同事务提交;写审计失败整笔回滚 |
102
+ | 命令拒绝 | FORBIDDEN / NOT_FOUND / CONSTRAINT_VIOLATED 异步尽力记录;失败有日志,不宣称所有错误均已留痕 |
103
+ | 密码登录失败 | 等待尝试写审计;失败有日志,请求仍拒绝登录,属于尽力记录 |
104
+ | get / list / related 的潜在敏感明文 | 具备相应 unmask 能力时先完成可靠审计再交付;失败拒绝结果 |
105
+ | 自定义 query | 按 reads 保守审计潜在敏感明文;声明 audit.purpose 时另外可靠记录目的;任一可靠审计失败均拒绝交付 |
106
+ | 导出生成、下载 | 可靠审计;下载还复核当前权限与访问指纹;失败不交付文件 |
107
+ | 导入任务及成功行 | 任务提交与目标命令按各自事务审计;异步逐行/原子方式见任务协议 |
108
+ | 普通掩码读取、诊断元数据 | 默认无统一专门审计 |
109
+
110
+ 成功登录、step-up 和退出目前没有统一专门审计。审计保证按入口区分,不能把“有审计”理解为每一种请求都可靠留痕。可靠读取失败使用 `CAGE-AUDIT-001`,凭 requestId 定位数据库与权限故障;默认日志只保留允许的执行元数据,错误文本、原始 payload 与外部响应不进入默认结构化日志。
111
+
87
112
  ## 数据库连接属于框架核心能力
88
113
 
89
114
  项目提供连接配置,框架管理连接池、事务、上下文变量和 SQL。普通业务不需要自己编写 `pg.Pool`、表仓储或连接释放逻辑。
@@ -103,6 +128,7 @@ APP、READ、MIGRATE 指向**同一个已经存在的数据库**,用不同账
103
128
  | `init` | 角色、schema、授权、随机凭证及 `.env` | 安装 PG、启动 Docker、创建数据库、创建业务表 |
104
129
  | `gen` | 本地 contracts、SQL、状态图、迁移草稿 | 应用数据库迁移 |
105
130
  | `migrate` | 已有库的表、索引、安全视图、迁移历史 | 每次启动新建数据库 |
131
+ | `bootstrap` | 显式初始化组织,将初始凭据交付到新的受限文件 | 自动授予业务能力、日常启动时重置密码 |
106
132
  | `dev` | 连接已有库,启动 API、worker、Web,监听声明重新生成 | 自动迁移或内嵌临时数据库兜底 |
107
133
 
108
134
  读路径使用 `safe.*`,业务表位于 `core.*`,组织和运行数据位于 `sys.*`。安全视图结合会话上下文执行范围过滤与掩码;手写报表也必须走这层。连接现有库不意味着自动把历史任意表变成 Cage 聚合,需要专门的映射与迁移方案。