cabloy 5.1.115 → 5.1.117

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 (207) hide show
  1. package/.cabloy-version +1 -1
  2. package/.claude/skills/cabloy-contract-loop/SKILL.md +8 -2
  3. package/.claude/skills/cabloy-contract-loop/evals/evals.json +6 -0
  4. package/.claude/skills/cabloy-contract-loop/references/contract-loop-map.md +13 -0
  5. package/.claude/skills/cabloy-contract-loop/references/resource-custom-state-pattern.md +16 -3
  6. package/.claude/skills/cabloy-contract-loop/references/verification-checklist.md +12 -0
  7. package/.github/workflows/vona-test-mysql.yml +6 -10
  8. package/.github/workflows/vona-test-pg.yml +9 -10
  9. package/.github/workflows/vona-test-sqlite3.yml +3 -0
  10. package/CHANGELOG.md +35 -0
  11. package/CLAUDE.md +2 -0
  12. package/cabloy-docs/.vitepress/config.mjs +6 -0
  13. package/cabloy-docs/ai/docs-skills-rules-mapping.md +7 -0
  14. package/cabloy-docs/backend/aop-overview.md +16 -24
  15. package/cabloy-docs/backend/backend-source-reading-roadmap.md +26 -2
  16. package/cabloy-docs/backend/controller-aop-guide.md +38 -14
  17. package/cabloy-docs/backend/logger-guide.md +1 -0
  18. package/cabloy-docs/backend/menu-guide.md +1 -1
  19. package/cabloy-docs/backend/orm-mutation-guide.md +36 -10
  20. package/cabloy-docs/backend/orm-select-guide.md +15 -10
  21. package/cabloy-docs/backend/rate-limit-guide.md +97 -0
  22. package/cabloy-docs/backend/telemetry-guide.md +93 -0
  23. package/cabloy-docs/backend/vona-source-reading-map.md +44 -1
  24. package/cabloy-docs/frontend/model-resource-owner-pattern.md +2 -0
  25. package/cabloy-docs/frontend/ssr-overview.md +2 -0
  26. package/cabloy-docs/fullstack/admin-resource-and-web-self-service.md +329 -0
  27. package/cabloy-docs/fullstack/contract-loop-playbook.md +1 -1
  28. package/cabloy-docs/fullstack/introduction.md +1 -0
  29. package/e2e/specs/a-commerce/commerce.spec.ts +479 -1
  30. package/package.json +1 -1
  31. package/vona/packages-cli/cli/package.json +1 -1
  32. package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/<%=argv.detailDtoBaseName%>.tsx_ +1 -1
  33. package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/<%=argv.detailDtoMutateName%>.tsx_ +2 -2
  34. package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/<%=argv.detailDtoResItemName%>.tsx_ +10 -10
  35. package/vona/packages-cli/cli-set-api/cli/templates/tools/masterDetail/boilerplate/dto/<%=argv.detailDtoViewName%>.tsx_ +2 -2
  36. package/vona/packages-cli/cli-set-api/package.json +1 -1
  37. package/vona/packages-cli/cli-set-api/src/lib/bean/cli.tools.masterDetail.ts +13 -4
  38. package/vona/packages-vona/vona/package.json +1 -1
  39. package/vona/packages-vona/vona-core/package.json +1 -1
  40. package/vona/packages-vona/vona-core/src/lib/core/logger/utils.ts +12 -3
  41. package/vona/packages-vona/vona-mock/package.json +1 -1
  42. package/vona/pnpm-lock.yaml +237 -164
  43. package/vona/src/backend/config/config/config.ts +1 -0
  44. package/vona/src/suite/a-commerce/modules/commerce-catalog/test/sku.test.ts +1 -1
  45. package/vona/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +81 -25
  46. package/vona/src/suite/a-commerce/modules/commerce-member/src/bean/ssrMenu.address.ts +30 -0
  47. package/vona/src/suite/a-commerce/modules/commerce-member/src/controller/address.ts +42 -20
  48. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineCreate.tsx +35 -0
  49. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineItem.tsx +39 -0
  50. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineReq.tsx +11 -0
  51. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineRes.tsx +11 -0
  52. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineUpdate.tsx +10 -0
  53. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressMineView.tsx +10 -0
  54. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressSelectResItem.tsx +1 -7
  55. package/vona/src/suite/a-commerce/modules/commerce-member/src/service/address.ts +43 -42
  56. package/vona/src/suite/a-commerce/modules/commerce-member/test/addressOwnership.test.ts +168 -127
  57. package/vona/src/suite/a-commerce/modules/commerce-payment/src/.metadata/index.ts +76 -3
  58. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.index.ts +3 -0
  59. package/vona/src/suite/a-commerce/modules/commerce-payment/src/bean/meta.version.ts +37 -0
  60. package/vona/src/suite/a-commerce/modules/commerce-payment/src/dto/paymentAttemptView.tsx +2 -2
  61. package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAttempt.tsx +5 -2
  62. package/vona/src/suite/a-commerce/modules/commerce-payment/src/entity/paymentAudit.tsx +54 -0
  63. package/vona/src/suite/a-commerce/modules/commerce-payment/src/model/paymentAudit.ts +10 -0
  64. package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/mockPaymentAdapter.ts +18 -0
  65. package/vona/src/suite/a-commerce/modules/commerce-payment/src/service/paymentAttempt.ts +22 -7
  66. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/dto/couponIssue.tsx +1 -1
  67. package/vona/src/suite/a-commerce/modules/commerce-promotion/src/service/coupon.ts +6 -1
  68. package/vona/src/suite/a-commerce/modules/commerce-promotion/test/couponReservation.test.ts +480 -8
  69. package/vona/src/suite/a-commerce/modules/commerce-siteadmin/test/ssrMenu.test.ts +14 -0
  70. package/vona/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +309 -3
  71. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.index.ts +2 -0
  72. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/meta.version.ts +16 -0
  73. package/vona/src/suite/a-commerce/modules/commerce-trade/src/bean/ssrMenu.order.ts +30 -0
  74. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/order.ts +69 -0
  75. package/vona/src/suite/a-commerce/modules/commerce-trade/src/controller/payment.ts +23 -0
  76. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/checkoutResult.tsx +4 -4
  77. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderDetail.tsx +72 -0
  78. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderMineReq.tsx +21 -0
  79. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderMineRes.tsx +11 -0
  80. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSelectReq.tsx +25 -0
  81. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSelectRes.tsx +11 -0
  82. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSelectResItem.tsx +38 -0
  83. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderShip.tsx +15 -0
  84. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/orderSummary.tsx +26 -0
  85. package/vona/src/suite/a-commerce/modules/{commerce-member/src/dto/addressCreate.tsx → commerce-trade/src/dto/orderView.tsx} +5 -17
  86. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/paymentOutcomeCreate.tsx +16 -0
  87. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/paymentOutcomeResult.tsx +29 -0
  88. package/vona/src/suite/a-commerce/modules/commerce-trade/src/dto/shipmentView.tsx +22 -0
  89. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/orderAudit.tsx +13 -7
  90. package/vona/src/suite/a-commerce/modules/commerce-trade/src/entity/shipment.tsx +28 -0
  91. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +1 -0
  92. package/vona/src/suite/a-commerce/modules/commerce-trade/src/model/shipment.ts +10 -0
  93. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/cart.ts +14 -0
  94. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/order.ts +436 -34
  95. package/vona/src/suite/a-commerce/modules/commerce-trade/src/service/stockBalance.ts +4 -3
  96. package/vona/src/suite/a-commerce/modules/commerce-trade/test/cartOwnership.test.ts +160 -80
  97. package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutReservation.test.ts +773 -2
  98. package/vona/src/suite/a-commerce/modules/commerce-trade/test/checkoutTransaction.test.ts +212 -0
  99. package/vona/src/suite/a-commerce/modules/commerce-trade/test/order.test.ts +37 -0
  100. package/vona/src/suite/a-commerce/modules/commerce-trade/test/orderSnapshot.test.ts +29 -5
  101. package/vona/src/suite/a-commerce/modules/commerce-trade/test/paymentOutcome.test.ts +714 -0
  102. package/vona/src/suite/a-commerce/modules/commerce-trade/test/reservationExpiry.test.ts +282 -9
  103. package/vona/src/suite/a-commerce/modules/commerce-trade/test/shipment.test.ts +260 -0
  104. package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockBalance.test.ts +15 -1
  105. package/vona/src/suite/a-commerce/modules/commerce-trade/test/stockReservation.test.ts +20 -2
  106. package/vona/src/suite/a-commerce/modules/commerce-trade/test/testLock.ts +11 -0
  107. package/vona/src/suite/a-home/modules/home-user/src/.metadata/index.ts +21 -0
  108. package/vona/src/suite/a-home/modules/home-user/src/controller/passportTest.ts +21 -0
  109. package/vona/src/suite/a-home/modules/home-user/test/passportTest.test.ts +77 -0
  110. package/vona/src/suite-vendor/a-vona/modules/a-broadcast/package.json +2 -1
  111. package/vona/src/suite-vendor/a-vona/modules/a-broadcast/src/service/broadcast.ts +84 -23
  112. package/vona/src/suite-vendor/a-vona/modules/a-broadcast/src/types/broadcast.ts +7 -1
  113. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  114. package/vona/src/suite-vendor/a-vona/modules/a-core/src/lib/core.ts +8 -0
  115. package/vona/src/suite-vendor/a-vona/modules/a-executor/package.json +4 -2
  116. package/vona/src/suite-vendor/a-vona/modules/a-executor/src/service/executor.ts +68 -53
  117. package/vona/src/suite-vendor/a-vona/modules/a-orm/cli/model/metadata/generate.ts +10 -0
  118. package/vona/src/suite-vendor/a-vona/modules/a-orm/package.json +1 -1
  119. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_cache.ts +12 -0
  120. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_crud_inner.ts +3 -0
  121. package/vona/src/suite-vendor/a-vona/modules/a-orm/src/lib/bean.model/bean.model_utils.ts +1 -1
  122. package/vona/src/suite-vendor/a-vona/modules/a-queue/package.json +4 -2
  123. package/vona/src/suite-vendor/a-vona/modules/a-queue/src/service/queue.ts +103 -54
  124. package/vona/src/suite-vendor/a-vona/modules/a-queue/src/types/queue.ts +5 -0
  125. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/package.json +48 -0
  126. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/.metadata/index.ts +100 -0
  127. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/.metadata/this.ts +2 -0
  128. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/bean/interceptor.rateLimit.ts +72 -0
  129. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/index.ts +2 -0
  130. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/main.ts +28 -0
  131. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/service/rateLimit.ts +75 -0
  132. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/types/index.ts +1 -0
  133. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/src/types/rateLimit.ts +19 -0
  134. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/tsconfig.build.json +11 -0
  135. package/vona/src/suite-vendor/a-vona/modules/a-ratelimit/tsconfig.json +7 -0
  136. package/vona/src/suite-vendor/a-vona/modules/a-redis/package.json +1 -1
  137. package/vona/src/suite-vendor/a-vona/modules/a-redis/src/service/redis.ts +1 -2
  138. package/vona/src/suite-vendor/a-vona/modules/a-redis/src/types/redis.ts +1 -0
  139. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/package.json +63 -0
  140. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/.metadata/index.ts +132 -0
  141. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/.metadata/this.ts +2 -0
  142. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/bean.telemetry.ts +71 -0
  143. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/bean/middlewareSystem.trace.ts +73 -0
  144. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/config/config.ts +83 -0
  145. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/index.ts +2 -0
  146. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/lib/ingress.ts +34 -0
  147. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/monkey.ts +22 -0
  148. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/service/telemetry.ts +201 -0
  149. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/types/index.ts +1 -0
  150. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/src/types/telemetry.ts +22 -0
  151. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/test/telemetry.test.ts +339 -0
  152. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/tsconfig.build.json +11 -0
  153. package/vona/src/suite-vendor/a-vona/modules/a-telemetry/tsconfig.json +7 -0
  154. package/vona/src/suite-vendor/a-vona/package.json +3 -1
  155. package/vona/src/suite-vendor/a-vona/tsconfig.json +3 -0
  156. package/zova/package.original.json +1 -1
  157. package/zova/packages-cli/cli/package.json +2 -2
  158. package/zova/packages-cli/cli-set-front/package.json +1 -1
  159. package/zova/packages-cli/cli-set-front/src/lib/bean/cli.bin.buildRest.ts +3 -2
  160. package/zova/packages-zova/zova/package.json +2 -2
  161. package/zova/pnpm-lock.yaml +87 -127
  162. package/zova/src/front/config/config/config.cabloyCommerce.ts +4 -0
  163. package/zova/src/suite/a-commerce/modules/commerce-member/cli/openapi.config.ts +9 -1
  164. package/zova/src/suite/a-commerce/modules/commerce-member/src/.metadata/index.ts +16 -0
  165. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/commerceMemberAddress.ts +94 -50
  166. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/schemas.ts +162 -52
  167. package/zova/src/suite/a-commerce/modules/commerce-member/src/api/openapi/types.ts +2656 -1298
  168. package/zova/src/suite/a-commerce/modules/commerce-member/src/apiSchema/commerceMemberAddress.ts +23 -13
  169. package/zova/src/suite/a-commerce/modules/commerce-member/src/model/addressMine.ts +67 -0
  170. package/zova/src/suite/a-commerce/modules/commerce-member/src/page/address/controller.tsx +21 -11
  171. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/schemas.ts +40 -32
  172. package/zova/src/suite/a-commerce/modules/commerce-promotion/src/api/openapi/types.ts +638 -633
  173. package/zova/src/suite/a-commerce/modules/commerce-trade/cli/openapi.config.ts +6 -0
  174. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/component/tableCellActionShip.ts +31 -0
  175. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/index.ts +248 -1
  176. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/page/checkout.ts +19 -0
  177. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/page/order.ts +19 -0
  178. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/page/orders.ts +19 -0
  179. package/zova/src/suite/a-commerce/modules/commerce-trade/src/.metadata/page/payment.ts +19 -0
  180. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradeOrder.ts +114 -0
  181. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/commerceTradePayment.ts +36 -0
  182. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/schemas.ts +75 -8
  183. package/zova/src/suite/a-commerce/modules/commerce-trade/src/api/openapi/types.ts +762 -46
  184. package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradeOrder.ts +35 -0
  185. package/zova/src/suite/a-commerce/modules/commerce-trade/src/apiSchema/commerceTradePayment.ts +13 -0
  186. package/zova/src/suite/a-commerce/modules/commerce-trade/src/bean/tableCell.actionShip.tsx +41 -0
  187. package/zova/src/suite/a-commerce/modules/commerce-trade/src/component/tableCellActionShip/controller.tsx +66 -0
  188. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/en-us.ts +2 -0
  189. package/zova/src/suite/a-commerce/modules/commerce-trade/src/config/locale/zh-cn.ts +2 -0
  190. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/order.ts +38 -0
  191. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/orderMine.ts +31 -0
  192. package/zova/src/suite/a-commerce/modules/commerce-trade/src/model/payment.ts +25 -0
  193. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/cart/controller.tsx +14 -3
  194. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/checkout/controller.tsx +124 -0
  195. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/order/controller.tsx +105 -0
  196. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/orders/controller.tsx +95 -0
  197. package/zova/src/suite/a-commerce/modules/commerce-trade/src/page/payment/controller.tsx +88 -0
  198. package/zova/src/suite/a-commerce/modules/commerce-trade/src/routes.ts +28 -0
  199. package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/command.delete.tsx +3 -2
  200. package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionUpdate.tsx +2 -1
  201. package/zova/src/suite/cabloy-basic/modules/basic-details/src/bean/tableCell.actionView.tsx +1 -1
  202. package/zova/src/suite/cabloy-basic/modules/basic-details/src/config/locale/en-us.ts +1 -0
  203. package/zova/src/suite/cabloy-basic/modules/basic-details/src/config/locale/zh-cn.ts +1 -0
  204. package/zova/src/suite-vendor/a-zova/modules/a-ssr/package.json +1 -1
  205. package/zova/src/suite-vendor/a-zova/modules/a-ssr/src/lib/ssr.ts +4 -0
  206. package/zova/src/suite-vendor/a-zova/package.json +2 -2
  207. package/vona/src/suite/a-commerce/modules/commerce-member/src/dto/addressUpdate.tsx +0 -39
package/.cabloy-version CHANGED
@@ -1 +1 @@
1
- 5.1.115
1
+ 5.1.117
@@ -117,6 +117,7 @@ For deeper reference material, read:
117
117
  - `references/contract-loop-map.md`
118
118
  - `references/verification-checklist.md`
119
119
  - `references/resource-custom-state-pattern.md`
120
+ - `../../../cabloy-docs/fullstack/admin-resource-and-web-self-service.md`
120
121
 
121
122
  ## Step 3: Identify the contract source of truth deliberately
122
123
 
@@ -237,9 +238,14 @@ After generation, inspect whether the frontend still needs follow-up in:
237
238
  Keep frontend follow-up thin:
238
239
 
239
240
  - use thin semantic model facades over generated consumers instead of re-declaring the contract
240
- - if a custom endpoint still belongs to an existing resource, prefer one resource-state owner instead of letting a module-local model create a second cache tree
241
+ - if a custom endpoint still belongs to an existing Admin Resource state boundary, prefer one resource-state owner instead of letting a module-local model create a second cache tree
241
242
 
242
- Reuse the resource-owned custom state pattern in `references/resource-custom-state-pattern.md`.
243
+ For one persisted domain with both Admin Resource and Web self-service consumers, choose the state boundary deliberately after backend contract truth and regeneration are established:
244
+
245
+ - **Admin/custom-resource branch:** the endpoint remains part of the Admin Resource authority, projection, and generic page state, so reuse `rest-resource.model.resource` through a thin semantic facade.
246
+ - **Separate Web self-service branch:** the consumer has different authority, server-derived owner scope, DTO projection, operation names, UX, or SSR behavior, so expose explicit self-service operations and use a dedicated Web model and purpose-built pages.
247
+
248
+ Share domain persistence and lifecycle logic, not necessarily HTTP projections or frontend state ownership. Read `../../../cabloy-docs/fullstack/admin-resource-and-web-self-service.md` for the complete architecture. Reuse the resource-owned custom state pattern in `references/resource-custom-state-pattern.md` for the Admin/custom-resource branch.
243
249
 
244
250
  ## Step 6: Keep edition-aware differences explicit
245
251
 
@@ -24,6 +24,12 @@
24
24
  "prompt": "The frontend model and generated SDK in Cabloy Basic look stale after a backend response change, but I am not sure whether the real bug is in backend OpenAPI output or in skipped frontend regeneration. Diagnose the contract loop the Cabloy way.",
25
25
  "expected_output": "Handles reverse stale-consumer detection, checks backend source-of-truth first, avoids hand-patching frontend types as the primary fix, and proposes regeneration or backend correction based on the contract path.",
26
26
  "files": []
27
+ },
28
+ {
29
+ "id": 5,
30
+ "prompt": "One persisted Order domain needs read-only Admin select/view operations through presetResource and customer Web list/detail reads. Should Admin and Web share one API and ModelResource, or should I use mine/viewMine-style Web operations and a separate Web model? Include the Cabloy contract-loop steps.",
31
+ "expected_output": "Keeps one domain and persistence boundary while separating Admin select/view and explicit owner-scoped Web operations with audience-specific DTOs. Keeps ModelResource as the Admin Resource owner and accepts a dedicated Web model/pages only for the distinct self-service state. Requires backend-first OpenAPI inspection, constrained regeneration for all owned operations, and verification of Admin authorization, Web owner scope before pagination, SSR admission where relevant, and both consumer paths. Rejects forcing Web through presetResource and rejects a parallel Admin cache owner for an ordinary custom Admin action.",
32
+ "files": []
27
33
  }
28
34
  ]
29
35
  }
@@ -92,6 +92,19 @@ Practical check after generation:
92
92
  - confirm the generated API files only contain the intended resource operations
93
93
  - confirm the module metadata and exports were not polluted by unrelated APIs
94
94
 
95
+ ### Dual-audience resource ownership
96
+
97
+ One persisted domain may deliberately own both conventional Admin Resource operations and explicit Web self-service operations. Include every intended operation in the constrained `operations.match` slice; this remains one forward-chain contract, not competing generated contracts or consumer drift.
98
+
99
+ After generation, choose the consumer shape by boundary:
100
+
101
+ | Consumer | Generated-contract follow-up |
102
+ | --- | --- |
103
+ | Admin custom operation with the same authority, projection, and Resource page semantics | thin module facade → `rest-resource.model.resource` → schema-driven Resource UI |
104
+ | Web self-service operation with different authority, server-derived owner scope, DTO projection, or page/SSR semantics | dedicated Web model → purpose-built self-service pages |
105
+
106
+ The Web model is valid only for the separate self-service state domain. It must not become a parallel owner for Admin `select`/`view`, schemas, permissions, or generic Resource page state. See `../../../../cabloy-docs/fullstack/admin-resource-and-web-self-service.md` for the complete architecture.
107
+
95
108
  ## Forward chain artifact map
96
109
 
97
110
  1. backend contract source
@@ -14,13 +14,13 @@ Typical examples:
14
14
 
15
15
  ## The ownership rule
16
16
 
17
- Prefer one owner for all resource-bound server state.
17
+ Prefer one owner for all server state in the same Admin Resource boundary.
18
18
 
19
- In this codebase, the preferred owner is:
19
+ In this codebase, the preferred Admin Resource owner is:
20
20
 
21
21
  - `rest-resource.model.resource`
22
22
 
23
- Do not let a module-local model become a second state owner for the same resource rows unless the boundary is explicitly intentional.
23
+ Do not let a module-local model become a second state owner for the same Admin resource rows unless the boundary is explicitly intentional.
24
24
 
25
25
  ## The split to avoid
26
26
 
@@ -136,6 +136,19 @@ A separate owner may still be fine when the data is not really part of the resou
136
136
 
137
137
  Even then, be explicit about the boundary.
138
138
 
139
+ ## Admin Resource and Web self-service boundary
140
+
141
+ A dedicated Web model is also valid when one persisted domain exposes a genuinely separate, customer-scoped self-service contract.
142
+
143
+ Use that branch only when all of these are true:
144
+
145
+ - the Web operations and DTOs are distinct from Admin `select`/`view` semantics;
146
+ - the server derives owner scope from the authenticated user and preserves normal instance scope;
147
+ - the Web model owns only its self-service query keys and purpose-built page state, not Admin Resource schemas, permissions, or generic page state;
148
+ - Web UX or private SSR behavior needs a dedicated admission and post-hydration boundary.
149
+
150
+ This does not justify a parallel owner for a custom Admin endpoint. It establishes a separate Web state domain beside the Admin Resource boundary. Read `../../../../cabloy-docs/fullstack/admin-resource-and-web-self-service.md` for the complete cross-stack pattern.
151
+
139
152
  ## Quick checklist
140
153
 
141
154
  1. confirm whether the resource pages already use `rest-resource.model.resource`
@@ -21,6 +21,18 @@ After a contract-loop change, verify the branch that actually applies.
21
21
  - `npm run tsc`
22
22
  - `npm run build`
23
23
 
24
+ ### Dual-audience resource verification
25
+
26
+ When one persisted domain has both Admin Resource and Web self-service consumers, also verify:
27
+
28
+ - every intended Admin and Web operation appears in emitted OpenAPI and the constrained generated SDK slice
29
+ - Admin controller authorization and the intended read-only or CRUD action surface remain independent of menu visibility
30
+ - Web requests cannot choose owner or tenant scope; the service applies owner and visibility predicates before count, sorting, and pagination
31
+ - Web DTOs omit owner, tenant, and operational-only fields
32
+ - Admin custom state remains in `ModelResource`; a dedicated Web model owns only the distinct self-service cache domain
33
+ - private Web SSR keeps a hydration-equivalent neutral shell until an explicit admission or post-hydration boundary
34
+ - targeted Admin and authenticated Web flow checks cover both audiences when both contracts changed
35
+
24
36
  ## Reverse chain verification
25
37
 
26
38
  - frontend-owned source is correct
@@ -35,16 +35,12 @@ jobs:
35
35
  - name: init
36
36
  run: npm run init
37
37
  - name: run Commerce lifecycle gate
38
- run: >-
39
- DATABASE_DEFAULT_CLIENT=mysql DATABASE_CLIENT_MYSQL_HOST=127.0.0.1 DATABASE_CLIENT_MYSQL_PASSWORD=root npm run vona :bin:test --
40
- commerce-trade/test/stockBalance.test.ts
41
- commerce-trade/test/stockReservation.test.ts
42
- commerce-trade/test/stockAudit.test.ts
43
- commerce-promotion/test/couponReservation.test.ts
44
- commerce-payment/test/paymentAttempt.test.ts
45
- commerce-trade/test/checkoutReservation.test.ts
46
- commerce-trade/test/reservationExpiry.test.ts
47
- --flavor=normal
38
+ run: |
39
+ DATABASE_DEFAULT_CLIENT=mysql DATABASE_CLIENT_MYSQL_HOST=127.0.0.1 DATABASE_CLIENT_MYSQL_PASSWORD=root npm run vona :bin:test -- commerce-trade/test/stockBalance.test.ts commerce-trade/test/stockReservation.test.ts commerce-trade/test/stockAudit.test.ts --flavor=normal
40
+ DATABASE_DEFAULT_CLIENT=mysql DATABASE_CLIENT_MYSQL_HOST=127.0.0.1 DATABASE_CLIENT_MYSQL_PASSWORD=root npm run vona :bin:test -- commerce-promotion/test/couponReservation.test.ts commerce-payment/test/paymentAttempt.test.ts --flavor=normal
41
+ DATABASE_DEFAULT_CLIENT=mysql DATABASE_CLIENT_MYSQL_HOST=127.0.0.1 DATABASE_CLIENT_MYSQL_PASSWORD=root npm run vona :bin:test -- commerce-trade/test/checkoutReservation.test.ts commerce-trade/test/checkoutTransaction.test.ts --flavor=normal
42
+ DATABASE_DEFAULT_CLIENT=mysql DATABASE_CLIENT_MYSQL_HOST=127.0.0.1 DATABASE_CLIENT_MYSQL_PASSWORD=root npm run vona :bin:test -- commerce-trade/test/orderSnapshot.test.ts --flavor=normal
43
+ DATABASE_DEFAULT_CLIENT=mysql DATABASE_CLIENT_MYSQL_HOST=127.0.0.1 DATABASE_CLIENT_MYSQL_PASSWORD=root npm run vona :bin:test -- commerce-trade/test/reservationExpiry.test.ts --flavor=normal
48
44
  working-directory: vona
49
45
  - run: DATABASE_DEFAULT_CLIENT=mysql DATABASE_CLIENT_MYSQL_HOST=127.0.0.1 DATABASE_CLIENT_MYSQL_PASSWORD=root npm run test
50
46
  working-directory: vona
@@ -35,16 +35,15 @@ jobs:
35
35
  - name: init
36
36
  run: npm run init
37
37
  - name: run Commerce stock contention gate
38
- run: >-
39
- DATABASE_DEFAULT_CLIENT=pg npm run vona :bin:test --
40
- commerce-trade/test/stockBalance.test.ts
41
- commerce-trade/test/stockReservation.test.ts
42
- commerce-trade/test/stockAudit.test.ts
43
- commerce-promotion/test/couponReservation.test.ts
44
- commerce-payment/test/paymentAttempt.test.ts
45
- commerce-trade/test/checkoutReservation.test.ts
46
- commerce-trade/test/reservationExpiry.test.ts
47
- --flavor=normal
38
+ run: |
39
+ DATABASE_DEFAULT_CLIENT=pg npm run vona :bin:test -- commerce-trade/test/stockBalance.test.ts commerce-trade/test/stockReservation.test.ts commerce-trade/test/stockAudit.test.ts --flavor=normal
40
+ DATABASE_DEFAULT_CLIENT=pg npm run vona :bin:test -- commerce-promotion/test/couponReservation.test.ts commerce-payment/test/paymentAttempt.test.ts --flavor=normal
41
+ DATABASE_DEFAULT_CLIENT=pg npm run vona :bin:test -- commerce-trade/test/checkoutReservation.test.ts commerce-trade/test/checkoutTransaction.test.ts --flavor=normal
42
+ DATABASE_DEFAULT_CLIENT=pg npm run vona :bin:test -- commerce-trade/test/orderSnapshot.test.ts --flavor=normal
43
+ DATABASE_DEFAULT_CLIENT=pg npm run vona :bin:test -- commerce-trade/test/reservationExpiry.test.ts --flavor=normal
44
+ working-directory: vona
45
+ - name: run Commerce payment and shipment lifecycle gate
46
+ run: DATABASE_DEFAULT_CLIENT=pg npm run vona :bin:test -- commerce-trade/test/paymentOutcome.test.ts commerce-trade/test/reservationExpiry.test.ts commerce-trade/test/shipment.test.ts --flavor=normal
48
47
  working-directory: vona
49
48
  - run: DATABASE_DEFAULT_CLIENT=pg npm run test
50
49
  working-directory: vona
@@ -27,6 +27,9 @@ jobs:
27
27
  version: 11.5.2
28
28
  - name: init
29
29
  run: npm run init
30
+ - name: run Commerce payment and shipment lifecycle gate
31
+ run: DATABASE_DEFAULT_CLIENT=sqlite3 npm run vona :bin:test -- commerce-trade/test/paymentOutcome.test.ts commerce-trade/test/reservationExpiry.test.ts commerce-trade/test/shipment.test.ts --flavor=normal
32
+ working-directory: vona
30
33
  - run: DATABASE_DEFAULT_CLIENT=sqlite3 npm run test
31
34
  working-directory: vona
32
35
  - run: DATABASE_DEFAULT_CLIENT=sqlite3 npm run build -- --flavor=ci
package/CHANGELOG.md CHANGED
@@ -1,5 +1,40 @@
1
1
  # Changelog
2
2
 
3
+ ## 5.1.117
4
+
5
+ ### Features
6
+
7
+ - Add detailed CLI output.
8
+ - Add telemetry support, including secure ingress propagation.
9
+ - Add tracing capabilities.
10
+ - Add Redis-backed rate limiting.
11
+ - Add dual-audience resource guidance.
12
+
13
+ ### Bug Fixes
14
+
15
+ - Fix REST module subpath bundle declarations.
16
+ - Complete Commerce payment flow validation and evidence coverage.
17
+
18
+ ### Improvements
19
+
20
+ - Simplify the Redis rate-limit command implementation.
21
+ - Clarify the Vona AOP request path documentation.
22
+ - Update dependency lockfiles and package metadata.
23
+
24
+ ## 5.1.116
25
+
26
+ ### Features
27
+
28
+ - Update platform capabilities and integrations.
29
+
30
+ ### Bug Fixes
31
+
32
+ - Stabilize Commerce trade transactions and associated tests.
33
+
34
+ ### Improvements
35
+
36
+ - Update address ownership test coverage.
37
+
3
38
  ## 5.1.115
4
39
 
5
40
  ### Improvements
package/CLAUDE.md CHANGED
@@ -46,6 +46,7 @@ Before inventing a custom implementation path:
46
46
  - Treat contract-loop work as one of four branches: forward chain, reverse chain, consumer drift, or local dependency drift.
47
47
  - For the forward chain, change backend contract truth first and regenerate frontend consumers rather than hand-patching them.
48
48
  - After forward regeneration, keep frontend follow-up thin: prefer semantic model facades and reuse the existing resource-owner when the custom API still belongs to the same resource.
49
+ - When one persisted business resource serves both Admin Resource and Web self-service consumers, keep one domain and persistence boundary; where audience authority or experience differs, split API/DTO contracts, server-side scope, frontend state ownership, and page architecture—reuse the generic Resource owner for Admin and use dedicated state/pages for a genuinely distinct Web self-service contract.
49
50
  - For the reverse chain, always run the relevant Zova build first, then run `npm run deps:vona`: use `npm run build:zova:admin` for Admin changes, and also run `npm run build:zova:web` when the Web flavor is affected. Do not treat `build:rest:*` alone as sufficient, because the SSR bundle and rest output must move together.
50
51
  - If the generated `.zova-rest` artifacts already contain the expected changes but Vona consumers still see stale types after `npm run deps:vona`, treat it as local dependency drift: delete `vona/node_modules` and reinstall dependencies before further debugging or hand-patching dependency links.
51
52
  - For Cabloy Start, apply the same reverse-chain logic but resolve the Start-specific flavor names and generated-output paths from the active Start repo before recommending commands.
@@ -57,6 +58,7 @@ Before inventing a custom implementation path:
57
58
  - Default to establishing such query state during render. Use `disableSuspenseOnInit: true` only for relatively stable query-backed state when you want to skip the init-time `query.suspense()` kick; it does not prevent query creation, fetches, or hydration-time rendering. If strict readiness is needed later, wait explicitly at the interaction boundary.
58
59
  - In SSR, keep server HTML and the client's hydration-time initial render equivalent. When server rendering intentionally omits private, cookie-unavailable, or browser-only state, keep the same neutral shell or placeholder through hydration and defer its query/load/render branch to an explicit post-hydration, admission, mounted, or interaction boundary.
59
60
  - Keep repo-wide AI rules in `CLAUDE.md` short and durable; put branching Zova analysis workflows in `.claude/skills/`.
61
+ - Do not modify `APP_NAME` in environment files unless the user explicitly requests it. When another worktree holds shared test resources, wait and retry later rather than changing application identity or related environment settings.
60
62
  - For SSR theme-sensitive frontend work, detect the active edition marker and UI library before making assumptions. Cabloy Basic currently means DaisyUI + Tailwind CSS assumptions; Cabloy Start currently means Vuetify assumptions.
61
63
  - In Web SSR without cookie-backed theme resolution, do not treat server reads of `$theme.dark`, `$theme.darkMode`, or `$token` as final browser truth. Keep theme-sensitive SSR branching hydration-tolerant or defer final theme-sensitive decisions to the client.
62
64
  - Do not assume Cabloy Basic and Cabloy Start use the same adapter-level SSR theme handoff. Verify the active theme handler and client hydration path before changing SSR theme behavior.
@@ -88,6 +88,10 @@ const fullstackGroups = [
88
88
  { text: 'Framework Performance', link: '/fullstack/framework-performance' },
89
89
  { text: 'Vona + Zova Integration', link: '/fullstack/vona-zova-integration' },
90
90
  { text: 'Contract Loop Playbook', link: '/fullstack/contract-loop-playbook' },
91
+ {
92
+ text: 'Admin Resource and Web Self-Service',
93
+ link: '/fullstack/admin-resource-and-web-self-service',
94
+ },
91
95
  {
92
96
  text: 'Backend Metadata to Frontend Table Actions',
93
97
  link: '/fullstack/backend-metadata-to-frontend-table-actions',
@@ -225,6 +229,7 @@ export default defineConfig({
225
229
  items: [
226
230
  { text: 'Auth Guide', link: '/backend/auth-guide' },
227
231
  { text: 'Captcha Guide', link: '/backend/captcha-guide' },
232
+ { text: 'Rate Limit Guide', link: '/backend/rate-limit-guide' },
228
233
  { text: 'User Access Guide', link: '/backend/user-access-guide' },
229
234
  { text: 'JWT Guide', link: '/backend/jwt-guide' },
230
235
  { text: 'Validation Guide', link: '/backend/validation-guide' },
@@ -238,6 +243,7 @@ export default defineConfig({
238
243
  { text: 'Error Guide', link: '/backend/error-guide' },
239
244
  { text: 'Event Guide', link: '/backend/event-guide' },
240
245
  { text: 'Logger Guide', link: '/backend/logger-guide' },
246
+ { text: 'Telemetry Guide', link: '/backend/telemetry-guide' },
241
247
  { text: 'Upload Guide', link: '/backend/upload-guide' },
242
248
  { text: 'Image Guide', link: '/backend/image-guide' },
243
249
  { text: 'File Guide', link: '/backend/file-guide' },
@@ -143,6 +143,13 @@ Use this quick rule:
143
143
  - full explanation → [Backend OpenAPI to Frontend SDK](/fullstack/openapi-to-sdk)
144
144
  - workflow steering → skill and rules can point to the docs and choose the right CLI path
145
145
 
146
+ ### Example: “How should one persisted resource serve Admin and Web users?”
147
+
148
+ - full architecture explanation → [Admin Resource and Web Self-Service](/fullstack/admin-resource-and-web-self-service)
149
+ - concise repo-wide default → `CLAUDE.md`
150
+ - contract regeneration, consumer-boundary choice, and verification routing → `cabloy-contract-loop`
151
+ - extend the existing contract-loop skill references and evals; do not create an Order-specific skill
152
+
146
153
  ### Example: “How should AI remove an existing module cleanly?”
147
154
 
148
155
  - public operational explanation → [Playbook: Module Removal](/ai/playbook-module-removal)
@@ -28,39 +28,31 @@ These families work together to shape request execution, parameter handling, err
28
28
 
29
29
  ## Execution model
30
30
 
31
- Two controller AOP families use an onion-style execution model:
31
+ Middleware and interceptors use onion-style execution: they can run logic before `next()` and after downstream controller work returns. Guards check access preconditions, pipes transform or validate request values, and filters handle exceptions on the error path.
32
32
 
33
- - **middleware**
34
- - **interceptor**
35
-
36
- That means they can run logic both before and after the controller action.
37
-
38
- Other controller AOP families participate in more specialized stages:
39
-
40
- - **guard** checks access or execution preconditions
41
- - **pipe** transforms or validates request values
42
- - **filter** handles exceptions and logging behavior
33
+ For a matched controller route, the inbound order is:
43
34
 
44
- A practical controller-path mental model is:
35
+ 1. **system middleware** before route matching
36
+ 2. **route matching**
37
+ 3. **global middleware** after a route matches and before authentication
38
+ 4. **guards**, including Passport authentication and authorization
39
+ 5. **interceptors**
40
+ 6. **argument extraction and pipes**
41
+ 7. **local middleware**
42
+ 8. **controller action**
45
43
 
46
- 1. system middleware runs before route matching
47
- 2. route matching happens
48
- 3. global and local middleware wrap the matched route
49
- 4. guards enforce access preconditions
50
- 5. pipes transform and validate incoming values
51
- 6. interceptors wrap controller execution
52
- 7. the controller action runs
53
- 8. filters handle thrown exceptions and logging customization when failures occur
44
+ After the action, middleware and interceptor after-`next()` work unwinds in reverse: local middleware, interceptors, then global middleware. Filters are not another successful inbound stage; they compose the exception-handling path when request execution throws.
54
45
 
55
- That model is the fastest way to decide which aspect family should own a change.
46
+ Choose the family by the state the concern needs: use system middleware for route-independent transport behavior, global middleware for matched-route work before authentication, a global interceptor for post-auth admission before body parsing, and local middleware for action-side wrapping after pipes.
56
47
 
57
48
  ## System, global, and local scope
58
49
 
59
50
  Controller AOP also varies by scope:
60
51
 
61
- - **system** middleware runs before route matching
62
- - **global** aspects are auto-loaded and can be applied broadly with runtime filters such as `match`, `ignore`, `mode`, or `flavor`
63
- - **local** aspects are attached directly to a controller class or action
52
+ - **system middleware** runs before route matching and has no matched-route metadata
53
+ - **global middleware** is auto-loaded after route matching but before guards; it can use runtime filters such as `match`, `ignore`, `mode`, or `flavor`
54
+ - **local middleware** is attached directly to a controller class or action and runs after pipes, immediately around the action-side suffix
55
+ - **global/local guards, interceptors, pipes, and filters** remain in their own family stages rather than sharing middleware placement
64
56
 
65
57
  Built-in aspects and shorthand decorators sit on top of the same general model.
66
58
 
@@ -77,6 +77,30 @@ Start here when your question is about:
77
77
  - if the question is “which scripts drive normal backend dev/build/start/test workflows?” -> read [Backend Scripts](/backend/scripts)
78
78
  - if the question is “which source files should I open first?” -> read [Vona Source Reading Map](/backend/vona-source-reading-map)
79
79
 
80
+ ## Topic cluster: controller request path and controller AOP
81
+
82
+ Start here when your question is about:
83
+
84
+ - what runs before route matching, after route matching, or after Passport authentication
85
+ - whether a concern belongs in system/global/local middleware, a guard, an interceptor, a pipe, or a filter
86
+ - why a global-onion decorator configures a route without changing its execution stage
87
+ - how controller/action options merge with application, instance, and dynamic onion configuration
88
+ - why an error is transformed or logged through filters
89
+
90
+ ### Concept pages first
91
+
92
+ - [AOP Overview](/backend/aop-overview)
93
+ - [Controller Guide](/backend/controller-guide)
94
+ - [Controller AOP Guide](/backend/controller-aop-guide)
95
+ - [Rate Limit Guide](/backend/rate-limit-guide) when request admission or quotas are involved
96
+
97
+ ### Best next step
98
+
99
+ - if the question is “how can this run before route matching?” -> read the system-middleware path in [Vona Source Reading Map](/backend/vona-source-reading-map)
100
+ - if the question is “does this run before or after Passport?” -> read the controller request pipeline path in [Vona Source Reading Map](/backend/vona-source-reading-map)
101
+ - if the question is “why does a route override retain other settings?” -> trace onion option composition in [Vona Source Reading Map](/backend/vona-source-reading-map)
102
+ - if the question is “why is an error transformed or logged?” -> follow the filter error-path follow-up in [Vona Source Reading Map](/backend/vona-source-reading-map)
103
+
80
104
  ## Topic cluster: resource and module CRUD chain
81
105
 
82
106
  Start here when your question is about:
@@ -110,11 +134,11 @@ It does **not** try to become the new entry page for every backend subsystem suc
110
134
  - workers
111
135
  - queues
112
136
  - websockets
113
- - auth
137
+ - the complete auth subsystem
114
138
  - cache
115
139
  - distributed runtime internals
116
140
 
117
- Those topics already have their own concept docs. This roadmap is only the first navigation layer for common source-reading tasks.
141
+ The controller request-path cluster covers where guards and Passport sit in route execution, not the complete authentication subsystem. Those topics already have their own concept docs. This roadmap is only the first navigation layer for common source-reading tasks.
118
142
 
119
143
  ## Final rule
120
144
 
@@ -32,15 +32,25 @@ These commands all go through the shared `:create:bean` entrypoint, but they tar
32
32
 
33
33
  ### Scope variants
34
34
 
35
- - **system middleware** runs before route matching
36
- - **global middleware** is auto-loaded and applied broadly
37
- - **local middleware** is attached directly to a controller class or action
35
+ - **system middleware** runs before route matching and has no matched-route metadata
36
+ - **global middleware** is auto-loaded after a route matches but before guards and Passport authentication
37
+ - **local middleware** is attached directly to a controller class or action and runs after pipes, around the action-side suffix
38
+
39
+ ### Inbound execution order
40
+
41
+ For a matched controller route, Vona enters these stages in order:
42
+
43
+ ```text
44
+ system middleware → route matching → global middleware → guard → interceptor → pipe → local middleware → action
45
+ ```
46
+
47
+ Middleware and interceptors use onion execution. Their after-`next()` work unwinds in reverse, so local middleware returns before interceptors, which return before global middleware. Filters are selected from the error path when request execution throws; they are not a normal inbound stage.
38
48
 
39
49
  ### Why system middleware is different
40
50
 
41
51
  System middleware executes before route matching, so it is the earliest controller-facing interception stage.
42
52
 
43
- This is where Vona places concerns such as not-found handling, request override behavior, app initialization, instance initialization, HTTP logging, CORS, and static-resource handling.
53
+ This is where Vona places concerns such as not-found handling, request override behavior, app initialization, instance initialization, HTTP logging, CORS, and static-resource handling. Use global middleware instead when the concern needs a matched route but must still run before authentication. Use local middleware when the concern is controller/action-specific and should see pipe-processed arguments.
44
54
 
45
55
  ### Representative local usage
46
56
 
@@ -54,6 +64,8 @@ This is where Vona places concerns such as not-found handling, request override
54
64
  @Aspect.middlewareGlobal('training-student:logger', { prefix: 'elapsed' })
55
65
  ```
56
66
 
67
+ `@Aspect.middlewareGlobal(...)` writes route options for an already registered global middleware. It does not add a local middleware or change that middleware’s pre-guard execution stage. The same distinction applies to `@Aspect.interceptorGlobal(...)`: it configures an existing global interceptor for the controller/action while the interceptor remains in the post-guard stage.
68
+
57
69
  ### Representative built-in usage
58
70
 
59
71
  ```typescript
@@ -125,8 +137,10 @@ Typical jobs include:
125
137
 
126
138
  ### Scope variants
127
139
 
128
- - **global interceptor** is auto-loaded and broadly configurable
129
- - **local interceptor** is attached directly to a controller class or action
140
+ - **global interceptor** is auto-loaded after guards and before pipes, local middleware, and controller execution
141
+ - **local interceptor** is attached directly to a controller class or action, runs inside global interceptors, and still precedes pipes
142
+
143
+ A global interceptor is the correct placement for post-auth admission that must reject before body parsing. For example, [`a-ratelimit:rateLimit`](/backend/rate-limit-guide) uses Passport-resolved identity, then runs before the request-body interceptor, pipes, local middleware, and action code.
130
144
 
131
145
  ### Representative local usage
132
146
 
@@ -140,7 +154,7 @@ Built-in interceptors can be used for framework-level response behavior, such as
140
154
 
141
155
  ## Pipe
142
156
 
143
- Pipes transform or validate request values before they reach controller logic.
157
+ Pipes transform or validate request values after interceptor entry and before local middleware and controller logic.
144
158
 
145
159
  ### Scope variants
146
160
 
@@ -190,7 +204,7 @@ For broader validation guidance, see [Validation Guide](/backend/validation-guid
190
204
 
191
205
  ## Filter
192
206
 
193
- Filters handle exceptions and logging behavior.
207
+ Filters handle exceptions and logging behavior on the error path. They are selected when request execution throws, rather than forming another successful inbound stage after the action.
194
208
 
195
209
  This is where request-path error customization becomes explicit.
196
210
 
@@ -230,15 +244,25 @@ Most controller aspect families support the same configuration ideas:
230
244
 
231
245
  That consistency is one of the most important reasons controller AOP stays scalable in Vona.
232
246
 
233
- ### Representative precedence model
247
+ ### Global-onion override and option ownership
248
+
249
+ A local use decorator such as `@Aspect.interceptor(...)` adds an onion to the local execution chain. A global use decorator such as `@Aspect.interceptorGlobal(...)` or `@Core.rateLimit(...)` instead writes controller/action route options for the named, already-global onion. It does not move that onion into a local stage.
250
+
251
+ Put concern-specific fields under the exact onion that consumes them. For example, `rateLimit` belongs to `a-ratelimit:rateLimit` options; putting that object in another middleware or interceptor’s options does not transfer the quota policy or activate the limiter.
234
252
 
235
- A representative precedence pattern is:
253
+ ### Effective option precedence
236
254
 
237
- - usage-site override
238
- - then app-config override in `config.onions`
239
- - then decorator default values
255
+ For normal object-valued onion options, lower-precedence values are deep-merged with later values in this order:
256
+
257
+ ```text
258
+ aspect defaults and application config.onions
259
+ → active-instance config.onions
260
+ → controller route options
261
+ → action route options
262
+ → controlled dynamic request or test overrides
263
+ ```
240
264
 
241
- For example, global middleware can define defaults in the bean, be overridden in app config, and then be overridden again at a specific controller action.
265
+ An action can therefore override only `rateLimit.limit` and inherit the rest of the controller or application policy. An action `enable: false` is an explicit exemption from a globally or controller-enabled onion. Primitive option scenes use the highest-precedence value instead of deep merging.
242
266
 
243
267
  ### Representative inspect patterns
244
268
 
@@ -210,6 +210,7 @@ Logging is closely connected to:
210
210
 
211
211
  - [Internal AOP Guide](/backend/internal-aop-guide) through built-in AOP helpers such as `@Core.log(...)`
212
212
  - [Runtime and Flavors](/backend/runtime-and-flavors) because log location, level, and operational behavior often vary by environment
213
+ - [Telemetry Guide](/backend/telemetry-guide) for distributed trace and span correlation in log entries; continue to use `$logger` and `$loggerChild(...)` as the logging APIs
213
214
 
214
215
  ## Implementation checks for backend logging changes
215
216
 
@@ -73,7 +73,7 @@ In the current repo implementation, the out-of-the-box menu controller is public
73
73
  - Omit `roles`, or use `roles: []`, to make an item visible to anonymous and authenticated callers.
74
74
  - A nonempty `roles` array is visible when the current Passport has at least one matching role name.
75
75
  - `roles` is server-only declaration metadata. It is filtered out before the API response and is not part of `IMenuItem`, OpenAPI, or generated frontend clients.
76
- - This controls navigation disclosure only. It never grants access to a page, controller action, API, or resource; those boundaries retain their own route and Passport/permission guards.
76
+ - This controls navigation disclosure only. It never grants access to a page, controller action, API, or resource; those boundaries retain their own route and Passport/permission guards. For the fullstack pattern that pairs an Admin `presetResource` entry with independently authorized Admin APIs and separately scoped Web self-service APIs, see [Admin Resource and Web Self-Service](/fullstack/admin-resource-and-web-self-service).
77
77
 
78
78
  SSR Site menu definitions are cached structurally by Site and locale. The framework keeps static role policy in that prepared cache, then creates a filtered response for each request without mutating the cached definition.
79
79
 
@@ -29,6 +29,32 @@ await this.scope.model.post.delete({ id: 1 });
29
29
 
30
30
  These operations are the clearest fit when the caller already knows the exact write intent.
31
31
 
32
+ ## Field presence in insert and update
33
+
34
+ For normal writable fields, insert and update intentionally interpret field presence differently:
35
+
36
+ | Field state | `insert` | `update` |
37
+ | -------------------------- | -------------------------------------------------- | -------------------------------- |
38
+ | Field is absent | Leave the column to normal insert/default behavior | Leave the stored value unchanged |
39
+ | Own field with `undefined` | Leave the column to normal insert/default behavior | Write SQL `NULL` |
40
+ | `null` | Write SQL `NULL` | Write SQL `NULL` |
41
+
42
+ An own `undefined` means the property exists on the JavaScript object but its value is `undefined`, such as `{ title: undefined }`. This lets an update distinguish an omitted field from an intentional clear:
43
+
44
+ ```typescript
45
+ await this.scope.model.post.update({
46
+ id,
47
+ title: undefined, // write SQL NULL
48
+ // stars is absent, so its stored value is unchanged
49
+ });
50
+ ```
51
+
52
+ ### JSON columns
53
+
54
+ JSON columns follow the same field-presence rules. Non-null values are persisted as JSON. An explicit `null` writes SQL `NULL`, not a JSON literal `null`. On update, an own `undefined` also clears the column to SQL `NULL`.
55
+
56
+ A JSON request body normally omits object properties whose value is `undefined`. This distinction therefore most often applies to in-process TypeScript model calls or server-side payload construction.
57
+
32
58
  ## Conditional update and delete paths
33
59
 
34
60
  Write operations do not have to target one row only by primary key.
@@ -56,7 +82,7 @@ await this.scope.model.post.delete({
56
82
  });
57
83
  ```
58
84
 
59
- That matters because the mutation layer still participates in the same structured query language used by select operations.
85
+ That matters because the mutation layer still participates in the same structured query language used by select operations. In `options.where`, an absent field, an own `undefined`, and `Op.omit` all omit that condition; `null` requests SQL `IS NULL`. See [ORM Select Guide](/backend/orm-select-guide#absent-undefined-and-null-in-where) for the detailed query contract.
60
86
 
61
87
  ## Bulk mutation operations
62
88
 
@@ -87,9 +113,12 @@ Instead of forcing callers to choose insert/update/delete up front, Vona can inf
87
113
 
88
114
  Representative logic:
89
115
 
90
- - no `id` → insert
91
- - `id` present → update
92
- - `id` present and `deleted: true` → delete
116
+ - `id` absent, `undefined`, or `null` → insert
117
+ - non-nullish `id` → update
118
+ - non-nullish `id` and `deleted: true` → delete
119
+ - `deleted: true` without a non-nullish `id` → ignore the item because no deletion target exists
120
+
121
+ A non-nullish identity is neither `null` nor `undefined`. `mutate` chooses by that usable identity value, not merely by whether the `id` key exists.
93
122
 
94
123
  Representative pattern:
95
124
 
@@ -182,13 +211,10 @@ The important rule is:
182
211
 
183
212
  There is also a useful argument-handling rule to remember:
184
213
 
185
- - default `eq` magic methods such as `getByName()` or `selectByName()` treat an omitted argument as `null`
186
- - non-`eq` magic methods such as `getByNameEqI()` require an explicit value and throw if the argument is omitted
187
-
188
- This aligns magic-method behavior with the current ORM `where` semantics:
214
+ - default `eq` magic methods such as `getByName()` or `selectByName()` treat an omitted or `undefined` argument as `null`
215
+ - non-`eq` magic methods such as `getByNameEqI()` require a concrete value and throw when the argument is omitted or `undefined`
189
216
 
190
- - `Op.omit` means omit a condition explicitly
191
- - `null` means SQL `IS NULL`
217
+ This is argument adaptation performed by the convenience method. It differs from a direct structured `where` object, where `{ name: undefined }` omits the condition. Use `Op.omit` to make direct condition omission explicit, and use `null` to request SQL `IS NULL`.
192
218
 
193
219
  That means mutation should stay conceptually grounded in the standard model methods even when convenience wrappers are present.
194
220