@zigrivers/scaffold 3.16.0 → 3.17.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 (377) hide show
  1. package/README.md +28 -0
  2. package/content/knowledge/backend/backend-fintech-broker-integration.md +244 -0
  3. package/content/knowledge/backend/backend-fintech-compliance.md +181 -0
  4. package/content/knowledge/backend/backend-fintech-data-modeling.md +210 -0
  5. package/content/knowledge/backend/backend-fintech-ledger.md +226 -0
  6. package/content/knowledge/backend/backend-fintech-observability.md +151 -0
  7. package/content/knowledge/backend/backend-fintech-order-lifecycle.md +213 -0
  8. package/content/knowledge/backend/backend-fintech-risk-management.md +150 -0
  9. package/content/knowledge/backend/backend-fintech-testing.md +197 -0
  10. package/content/knowledge/core/automated-review-tooling.md +10 -0
  11. package/content/knowledge/core/multi-service-api-contracts.md +634 -0
  12. package/content/knowledge/core/multi-service-architecture.md +492 -0
  13. package/content/knowledge/core/multi-service-auth.md +706 -0
  14. package/content/knowledge/core/multi-service-data-ownership.md +539 -0
  15. package/content/knowledge/core/multi-service-observability.md +545 -0
  16. package/content/knowledge/core/multi-service-resilience.md +710 -0
  17. package/content/knowledge/core/multi-service-task-decomposition.md +615 -0
  18. package/content/knowledge/core/multi-service-testing.md +728 -0
  19. package/content/methodology/backend-fintech.yml +46 -0
  20. package/content/methodology/custom-defaults.yml +6 -0
  21. package/content/methodology/deep.yml +6 -0
  22. package/content/methodology/multi-service-overlay.yml +103 -0
  23. package/content/methodology/mvp.yml +6 -0
  24. package/content/pipeline/architecture/service-ownership-map.md +83 -0
  25. package/content/pipeline/quality/cross-service-auth.md +96 -0
  26. package/content/pipeline/quality/cross-service-observability.md +104 -0
  27. package/content/pipeline/quality/integration-test-plan.md +106 -0
  28. package/content/pipeline/specification/inter-service-contracts.md +95 -0
  29. package/dist/cli/commands/adopt.cli-flags.test.js +20 -0
  30. package/dist/cli/commands/adopt.cli-flags.test.js.map +1 -1
  31. package/dist/cli/commands/adopt.d.ts.map +1 -1
  32. package/dist/cli/commands/adopt.js +11 -3
  33. package/dist/cli/commands/adopt.js.map +1 -1
  34. package/dist/cli/commands/complete.d.ts +1 -0
  35. package/dist/cli/commands/complete.d.ts.map +1 -1
  36. package/dist/cli/commands/complete.js +26 -8
  37. package/dist/cli/commands/complete.js.map +1 -1
  38. package/dist/cli/commands/dashboard.d.ts +1 -0
  39. package/dist/cli/commands/dashboard.d.ts.map +1 -1
  40. package/dist/cli/commands/dashboard.js +19 -6
  41. package/dist/cli/commands/dashboard.js.map +1 -1
  42. package/dist/cli/commands/decisions.d.ts +1 -0
  43. package/dist/cli/commands/decisions.d.ts.map +1 -1
  44. package/dist/cli/commands/decisions.js +18 -4
  45. package/dist/cli/commands/decisions.js.map +1 -1
  46. package/dist/cli/commands/info.d.ts +1 -0
  47. package/dist/cli/commands/info.d.ts.map +1 -1
  48. package/dist/cli/commands/info.js +25 -3
  49. package/dist/cli/commands/info.js.map +1 -1
  50. package/dist/cli/commands/init-from.test.d.ts +2 -0
  51. package/dist/cli/commands/init-from.test.d.ts.map +1 -0
  52. package/dist/cli/commands/init-from.test.js +315 -0
  53. package/dist/cli/commands/init-from.test.js.map +1 -0
  54. package/dist/cli/commands/init.d.ts +3 -0
  55. package/dist/cli/commands/init.d.ts.map +1 -1
  56. package/dist/cli/commands/init.js +239 -129
  57. package/dist/cli/commands/init.js.map +1 -1
  58. package/dist/cli/commands/init.test.js +20 -0
  59. package/dist/cli/commands/init.test.js.map +1 -1
  60. package/dist/cli/commands/next.d.ts +1 -0
  61. package/dist/cli/commands/next.d.ts.map +1 -1
  62. package/dist/cli/commands/next.js +40 -4
  63. package/dist/cli/commands/next.js.map +1 -1
  64. package/dist/cli/commands/next.test.js +151 -0
  65. package/dist/cli/commands/next.test.js.map +1 -1
  66. package/dist/cli/commands/reset.d.ts +1 -0
  67. package/dist/cli/commands/reset.d.ts.map +1 -1
  68. package/dist/cli/commands/reset.js +77 -29
  69. package/dist/cli/commands/reset.js.map +1 -1
  70. package/dist/cli/commands/rework.d.ts +1 -0
  71. package/dist/cli/commands/rework.d.ts.map +1 -1
  72. package/dist/cli/commands/rework.js +16 -2
  73. package/dist/cli/commands/rework.js.map +1 -1
  74. package/dist/cli/commands/run.d.ts +1 -0
  75. package/dist/cli/commands/run.d.ts.map +1 -1
  76. package/dist/cli/commands/run.js +65 -13
  77. package/dist/cli/commands/run.js.map +1 -1
  78. package/dist/cli/commands/run.test.js +192 -3
  79. package/dist/cli/commands/run.test.js.map +1 -1
  80. package/dist/cli/commands/skip.d.ts +1 -0
  81. package/dist/cli/commands/skip.d.ts.map +1 -1
  82. package/dist/cli/commands/skip.js +24 -7
  83. package/dist/cli/commands/skip.js.map +1 -1
  84. package/dist/cli/commands/status.d.ts +1 -0
  85. package/dist/cli/commands/status.d.ts.map +1 -1
  86. package/dist/cli/commands/status.js +51 -4
  87. package/dist/cli/commands/status.js.map +1 -1
  88. package/dist/cli/commands/status.test.js +128 -0
  89. package/dist/cli/commands/status.test.js.map +1 -1
  90. package/dist/cli/guards-coverage.test.d.ts +2 -0
  91. package/dist/cli/guards-coverage.test.d.ts.map +1 -0
  92. package/dist/cli/guards-coverage.test.js +26 -0
  93. package/dist/cli/guards-coverage.test.js.map +1 -0
  94. package/dist/cli/guards-integration.test.d.ts +2 -0
  95. package/dist/cli/guards-integration.test.d.ts.map +1 -0
  96. package/dist/cli/guards-integration.test.js +178 -0
  97. package/dist/cli/guards-integration.test.js.map +1 -0
  98. package/dist/cli/guards.d.ts +13 -0
  99. package/dist/cli/guards.d.ts.map +1 -0
  100. package/dist/cli/guards.js +70 -0
  101. package/dist/cli/guards.js.map +1 -0
  102. package/dist/cli/guards.test.d.ts +2 -0
  103. package/dist/cli/guards.test.d.ts.map +1 -0
  104. package/dist/cli/guards.test.js +136 -0
  105. package/dist/cli/guards.test.js.map +1 -0
  106. package/dist/cli/init-flag-families.d.ts +1 -1
  107. package/dist/cli/init-flag-families.d.ts.map +1 -1
  108. package/dist/cli/init-flag-families.js +4 -1
  109. package/dist/cli/init-flag-families.js.map +1 -1
  110. package/dist/cli/init-flag-families.test.js +10 -0
  111. package/dist/cli/init-flag-families.test.js.map +1 -1
  112. package/dist/cli/shutdown.d.ts +2 -3
  113. package/dist/cli/shutdown.d.ts.map +1 -1
  114. package/dist/cli/shutdown.js +14 -11
  115. package/dist/cli/shutdown.js.map +1 -1
  116. package/dist/cli/shutdown.test.js +2 -4
  117. package/dist/cli/shutdown.test.js.map +1 -1
  118. package/dist/config/schema.d.ts +12122 -288
  119. package/dist/config/schema.d.ts.map +1 -1
  120. package/dist/config/schema.js +74 -79
  121. package/dist/config/schema.js.map +1 -1
  122. package/dist/config/schema.test.js +230 -1
  123. package/dist/config/schema.test.js.map +1 -1
  124. package/dist/config/validators/backend.d.ts +4 -0
  125. package/dist/config/validators/backend.d.ts.map +1 -0
  126. package/dist/config/validators/backend.js +14 -0
  127. package/dist/config/validators/backend.js.map +1 -0
  128. package/dist/config/validators/browser-extension.d.ts +4 -0
  129. package/dist/config/validators/browser-extension.d.ts.map +1 -0
  130. package/dist/config/validators/browser-extension.js +24 -0
  131. package/dist/config/validators/browser-extension.js.map +1 -0
  132. package/dist/config/validators/cli.d.ts +4 -0
  133. package/dist/config/validators/cli.d.ts.map +1 -0
  134. package/dist/config/validators/cli.js +14 -0
  135. package/dist/config/validators/cli.js.map +1 -0
  136. package/dist/config/validators/data-pipeline.d.ts +4 -0
  137. package/dist/config/validators/data-pipeline.d.ts.map +1 -0
  138. package/dist/config/validators/data-pipeline.js +14 -0
  139. package/dist/config/validators/data-pipeline.js.map +1 -0
  140. package/dist/config/validators/game.d.ts +4 -0
  141. package/dist/config/validators/game.d.ts.map +1 -0
  142. package/dist/config/validators/game.js +14 -0
  143. package/dist/config/validators/game.js.map +1 -0
  144. package/dist/config/validators/index.d.ts +7 -0
  145. package/dist/config/validators/index.d.ts.map +1 -0
  146. package/dist/config/validators/index.js +27 -0
  147. package/dist/config/validators/index.js.map +1 -0
  148. package/dist/config/validators/library.d.ts +4 -0
  149. package/dist/config/validators/library.d.ts.map +1 -0
  150. package/dist/config/validators/library.js +25 -0
  151. package/dist/config/validators/library.js.map +1 -0
  152. package/dist/config/validators/ml.d.ts +4 -0
  153. package/dist/config/validators/ml.d.ts.map +1 -0
  154. package/dist/config/validators/ml.js +31 -0
  155. package/dist/config/validators/ml.js.map +1 -0
  156. package/dist/config/validators/mobile-app.d.ts +4 -0
  157. package/dist/config/validators/mobile-app.d.ts.map +1 -0
  158. package/dist/config/validators/mobile-app.js +14 -0
  159. package/dist/config/validators/mobile-app.js.map +1 -0
  160. package/dist/config/validators/registry.test.d.ts +2 -0
  161. package/dist/config/validators/registry.test.d.ts.map +1 -0
  162. package/dist/config/validators/registry.test.js +26 -0
  163. package/dist/config/validators/registry.test.js.map +1 -0
  164. package/dist/config/validators/research.d.ts +4 -0
  165. package/dist/config/validators/research.d.ts.map +1 -0
  166. package/dist/config/validators/research.js +24 -0
  167. package/dist/config/validators/research.js.map +1 -0
  168. package/dist/config/validators/research.test.d.ts +2 -0
  169. package/dist/config/validators/research.test.d.ts.map +1 -0
  170. package/dist/config/validators/research.test.js +44 -0
  171. package/dist/config/validators/research.test.js.map +1 -0
  172. package/dist/config/validators/types.d.ts +19 -0
  173. package/dist/config/validators/types.d.ts.map +1 -0
  174. package/dist/config/validators/types.js +2 -0
  175. package/dist/config/validators/types.js.map +1 -0
  176. package/dist/config/validators/validators.test.d.ts +2 -0
  177. package/dist/config/validators/validators.test.d.ts.map +1 -0
  178. package/dist/config/validators/validators.test.js +25 -0
  179. package/dist/config/validators/validators.test.js.map +1 -0
  180. package/dist/config/validators/web-app.d.ts +4 -0
  181. package/dist/config/validators/web-app.d.ts.map +1 -0
  182. package/dist/config/validators/web-app.js +31 -0
  183. package/dist/config/validators/web-app.js.map +1 -0
  184. package/dist/core/assembly/context-gatherer.d.ts.map +1 -1
  185. package/dist/core/assembly/context-gatherer.js +4 -2
  186. package/dist/core/assembly/context-gatherer.js.map +1 -1
  187. package/dist/core/assembly/cross-reads.d.ts +58 -0
  188. package/dist/core/assembly/cross-reads.d.ts.map +1 -0
  189. package/dist/core/assembly/cross-reads.js +185 -0
  190. package/dist/core/assembly/cross-reads.js.map +1 -0
  191. package/dist/core/assembly/cross-reads.test.d.ts +2 -0
  192. package/dist/core/assembly/cross-reads.test.d.ts.map +1 -0
  193. package/dist/core/assembly/cross-reads.test.js +383 -0
  194. package/dist/core/assembly/cross-reads.test.js.map +1 -0
  195. package/dist/core/assembly/overlay-loader-structural.test.d.ts +2 -0
  196. package/dist/core/assembly/overlay-loader-structural.test.d.ts.map +1 -0
  197. package/dist/core/assembly/overlay-loader-structural.test.js +114 -0
  198. package/dist/core/assembly/overlay-loader-structural.test.js.map +1 -0
  199. package/dist/core/assembly/overlay-loader.d.ts +17 -3
  200. package/dist/core/assembly/overlay-loader.d.ts.map +1 -1
  201. package/dist/core/assembly/overlay-loader.js +75 -0
  202. package/dist/core/assembly/overlay-loader.js.map +1 -1
  203. package/dist/core/assembly/overlay-resolver.d.ts +2 -2
  204. package/dist/core/assembly/overlay-resolver.d.ts.map +1 -1
  205. package/dist/core/assembly/overlay-resolver.js.map +1 -1
  206. package/dist/core/assembly/overlay-resolver.test.js.map +1 -1
  207. package/dist/core/assembly/overlay-state-resolver.d.ts +5 -0
  208. package/dist/core/assembly/overlay-state-resolver.d.ts.map +1 -1
  209. package/dist/core/assembly/overlay-state-resolver.js +41 -1
  210. package/dist/core/assembly/overlay-state-resolver.js.map +1 -1
  211. package/dist/core/assembly/overlay-state-resolver.test.js +262 -0
  212. package/dist/core/assembly/overlay-state-resolver.test.js.map +1 -1
  213. package/dist/core/assembly/update-mode.d.ts +1 -0
  214. package/dist/core/assembly/update-mode.d.ts.map +1 -1
  215. package/dist/core/assembly/update-mode.js +17 -9
  216. package/dist/core/assembly/update-mode.js.map +1 -1
  217. package/dist/core/dependency/eligibility.d.ts +10 -1
  218. package/dist/core/dependency/eligibility.d.ts.map +1 -1
  219. package/dist/core/dependency/eligibility.js +19 -1
  220. package/dist/core/dependency/eligibility.js.map +1 -1
  221. package/dist/core/dependency/eligibility.test.js +82 -0
  222. package/dist/core/dependency/eligibility.test.js.map +1 -1
  223. package/dist/core/dependency/graph.d.ts +4 -1
  224. package/dist/core/dependency/graph.d.ts.map +1 -1
  225. package/dist/core/dependency/graph.js +7 -1
  226. package/dist/core/dependency/graph.js.map +1 -1
  227. package/dist/core/dependency/graph.test.js +29 -0
  228. package/dist/core/dependency/graph.test.js.map +1 -1
  229. package/dist/core/pipeline/global-steps.d.ts +7 -0
  230. package/dist/core/pipeline/global-steps.d.ts.map +1 -0
  231. package/dist/core/pipeline/global-steps.js +18 -0
  232. package/dist/core/pipeline/global-steps.js.map +1 -0
  233. package/dist/core/pipeline/resolver.d.ts +1 -0
  234. package/dist/core/pipeline/resolver.d.ts.map +1 -1
  235. package/dist/core/pipeline/resolver.js +51 -6
  236. package/dist/core/pipeline/resolver.js.map +1 -1
  237. package/dist/core/pipeline/types.d.ts +5 -1
  238. package/dist/core/pipeline/types.d.ts.map +1 -1
  239. package/dist/e2e/cross-service-references.test.d.ts +22 -0
  240. package/dist/e2e/cross-service-references.test.d.ts.map +1 -0
  241. package/dist/e2e/cross-service-references.test.js +188 -0
  242. package/dist/e2e/cross-service-references.test.js.map +1 -0
  243. package/dist/e2e/multi-service-pipeline.test.d.ts +10 -0
  244. package/dist/e2e/multi-service-pipeline.test.d.ts.map +1 -0
  245. package/dist/e2e/multi-service-pipeline.test.js +185 -0
  246. package/dist/e2e/multi-service-pipeline.test.js.map +1 -0
  247. package/dist/e2e/project-type-overlays.test.js +68 -0
  248. package/dist/e2e/project-type-overlays.test.js.map +1 -1
  249. package/dist/e2e/service-execution.test.d.ts +15 -0
  250. package/dist/e2e/service-execution.test.d.ts.map +1 -0
  251. package/dist/e2e/service-execution.test.js +219 -0
  252. package/dist/e2e/service-execution.test.js.map +1 -0
  253. package/dist/e2e/service-manifest.test.d.ts +19 -0
  254. package/dist/e2e/service-manifest.test.d.ts.map +1 -0
  255. package/dist/e2e/service-manifest.test.js +166 -0
  256. package/dist/e2e/service-manifest.test.js.map +1 -0
  257. package/dist/project/__frozen-schemas__/schema-v3.9.2.d.ts +224 -224
  258. package/dist/project/frontmatter.d.ts.map +1 -1
  259. package/dist/project/frontmatter.js +11 -0
  260. package/dist/project/frontmatter.js.map +1 -1
  261. package/dist/project/frontmatter.test.js +71 -0
  262. package/dist/project/frontmatter.test.js.map +1 -1
  263. package/dist/state/completion.d.ts +1 -1
  264. package/dist/state/completion.d.ts.map +1 -1
  265. package/dist/state/completion.js +10 -8
  266. package/dist/state/completion.js.map +1 -1
  267. package/dist/state/decision-logger.d.ts +3 -2
  268. package/dist/state/decision-logger.d.ts.map +1 -1
  269. package/dist/state/decision-logger.js +12 -11
  270. package/dist/state/decision-logger.js.map +1 -1
  271. package/dist/state/ensure-v3-migration.d.ts +9 -0
  272. package/dist/state/ensure-v3-migration.d.ts.map +1 -0
  273. package/dist/state/ensure-v3-migration.js +35 -0
  274. package/dist/state/ensure-v3-migration.js.map +1 -0
  275. package/dist/state/lock-manager.d.ts +5 -4
  276. package/dist/state/lock-manager.d.ts.map +1 -1
  277. package/dist/state/lock-manager.js +11 -11
  278. package/dist/state/lock-manager.js.map +1 -1
  279. package/dist/state/rework-manager.d.ts +1 -2
  280. package/dist/state/rework-manager.d.ts.map +1 -1
  281. package/dist/state/rework-manager.js +4 -5
  282. package/dist/state/rework-manager.js.map +1 -1
  283. package/dist/state/state-manager.d.ts +25 -1
  284. package/dist/state/state-manager.d.ts.map +1 -1
  285. package/dist/state/state-manager.js +86 -12
  286. package/dist/state/state-manager.js.map +1 -1
  287. package/dist/state/state-manager.test.js +278 -0
  288. package/dist/state/state-manager.test.js.map +1 -1
  289. package/dist/state/state-migration-v3.d.ts +22 -0
  290. package/dist/state/state-migration-v3.d.ts.map +1 -0
  291. package/dist/state/state-migration-v3.js +82 -0
  292. package/dist/state/state-migration-v3.js.map +1 -0
  293. package/dist/state/state-migration-v3.test.d.ts +2 -0
  294. package/dist/state/state-migration-v3.test.d.ts.map +1 -0
  295. package/dist/state/state-migration-v3.test.js +196 -0
  296. package/dist/state/state-migration-v3.test.js.map +1 -0
  297. package/dist/state/state-migration.d.ts.map +1 -1
  298. package/dist/state/state-migration.js +11 -6
  299. package/dist/state/state-migration.js.map +1 -1
  300. package/dist/state/state-migration.test.js +47 -2
  301. package/dist/state/state-migration.test.js.map +1 -1
  302. package/dist/state/state-path-resolver.d.ts +23 -0
  303. package/dist/state/state-path-resolver.d.ts.map +1 -0
  304. package/dist/state/state-path-resolver.js +36 -0
  305. package/dist/state/state-path-resolver.js.map +1 -0
  306. package/dist/state/state-path-resolver.test.d.ts +2 -0
  307. package/dist/state/state-path-resolver.test.d.ts.map +1 -0
  308. package/dist/state/state-path-resolver.test.js +78 -0
  309. package/dist/state/state-path-resolver.test.js.map +1 -0
  310. package/dist/state/state-version-dispatch.d.ts +17 -0
  311. package/dist/state/state-version-dispatch.d.ts.map +1 -0
  312. package/dist/state/state-version-dispatch.js +27 -0
  313. package/dist/state/state-version-dispatch.js.map +1 -0
  314. package/dist/state/state-version-dispatch.test.d.ts +2 -0
  315. package/dist/state/state-version-dispatch.test.d.ts.map +1 -0
  316. package/dist/state/state-version-dispatch.test.js +40 -0
  317. package/dist/state/state-version-dispatch.test.js.map +1 -0
  318. package/dist/types/config.d.ts +25 -3
  319. package/dist/types/config.d.ts.map +1 -1
  320. package/dist/types/config.test.js +13 -1
  321. package/dist/types/config.test.js.map +1 -1
  322. package/dist/types/dependency.d.ts +5 -0
  323. package/dist/types/dependency.d.ts.map +1 -1
  324. package/dist/types/frontmatter.d.ts +5 -0
  325. package/dist/types/frontmatter.d.ts.map +1 -1
  326. package/dist/types/lock.d.ts +1 -1
  327. package/dist/types/lock.d.ts.map +1 -1
  328. package/dist/types/state.d.ts +1 -1
  329. package/dist/types/state.d.ts.map +1 -1
  330. package/dist/utils/artifact-path.d.ts +19 -0
  331. package/dist/utils/artifact-path.d.ts.map +1 -0
  332. package/dist/utils/artifact-path.js +95 -0
  333. package/dist/utils/artifact-path.js.map +1 -0
  334. package/dist/utils/artifact-path.test.d.ts +2 -0
  335. package/dist/utils/artifact-path.test.d.ts.map +1 -0
  336. package/dist/utils/artifact-path.test.js +138 -0
  337. package/dist/utils/artifact-path.test.js.map +1 -0
  338. package/dist/utils/errors.d.ts +1 -1
  339. package/dist/utils/errors.d.ts.map +1 -1
  340. package/dist/utils/errors.js +5 -2
  341. package/dist/utils/errors.js.map +1 -1
  342. package/dist/utils/user-errors.d.ts +46 -0
  343. package/dist/utils/user-errors.d.ts.map +1 -0
  344. package/dist/utils/user-errors.js +76 -0
  345. package/dist/utils/user-errors.js.map +1 -0
  346. package/dist/utils/user-errors.test.d.ts +2 -0
  347. package/dist/utils/user-errors.test.d.ts.map +1 -0
  348. package/dist/utils/user-errors.test.js +74 -0
  349. package/dist/utils/user-errors.test.js.map +1 -0
  350. package/dist/validation/index.d.ts.map +1 -1
  351. package/dist/validation/index.js +16 -0
  352. package/dist/validation/index.js.map +1 -1
  353. package/dist/validation/index.test.js +48 -0
  354. package/dist/validation/index.test.js.map +1 -1
  355. package/dist/validation/state-validator.d.ts +5 -2
  356. package/dist/validation/state-validator.d.ts.map +1 -1
  357. package/dist/validation/state-validator.js +18 -20
  358. package/dist/validation/state-validator.js.map +1 -1
  359. package/dist/validation/state-validator.test.js +31 -2
  360. package/dist/validation/state-validator.test.js.map +1 -1
  361. package/dist/wizard/copy/backend.d.ts.map +1 -1
  362. package/dist/wizard/copy/backend.js +12 -0
  363. package/dist/wizard/copy/backend.js.map +1 -1
  364. package/dist/wizard/flags.d.ts +1 -0
  365. package/dist/wizard/flags.d.ts.map +1 -1
  366. package/dist/wizard/questions.d.ts.map +1 -1
  367. package/dist/wizard/questions.js +5 -1
  368. package/dist/wizard/questions.js.map +1 -1
  369. package/dist/wizard/questions.test.js +45 -2
  370. package/dist/wizard/questions.test.js.map +1 -1
  371. package/dist/wizard/wizard.d.ts +23 -0
  372. package/dist/wizard/wizard.d.ts.map +1 -1
  373. package/dist/wizard/wizard.js +85 -47
  374. package/dist/wizard/wizard.js.map +1 -1
  375. package/dist/wizard/wizard.test.js +186 -1
  376. package/dist/wizard/wizard.test.js.map +1 -1
  377. package/package.json +1 -1
@@ -0,0 +1,539 @@
1
+ ---
2
+ name: multi-service-data-ownership
3
+ description: Table ownership, shared-nothing data patterns, and event-driven synchronization
4
+ topics: [table-ownership, shared-nothing, event-driven-sync, data-partitioning, eventual-consistency]
5
+ ---
6
+
7
+ ## Summary
8
+
9
+ Data ownership is the most consequential architectural decision in a multi-service system. Getting it wrong causes cascading failures, data inconsistency, and coupling that defeats the purpose of service decomposition.
10
+
11
+ **Core principle:** Each service owns its data exclusively — no service reads another service's database directly, and no two services write to the same table.
12
+
13
+ **Data isolation levels** (strongest to weakest): separate clusters, separate databases, separate schemas, table prefix within a shared schema (not recommended).
14
+
15
+ **Cross-boundary data access patterns:**
16
+ - **Synchronous API call:** Simple but adds latency and temporal coupling.
17
+ - **Event-driven projection:** Service subscribes to events and maintains a local read-optimized copy. Eliminates runtime dependency but introduces eventual consistency.
18
+ - **API composition:** Gateway fans out to multiple services and merges results.
19
+
20
+ **Reliable event publishing** requires the outbox pattern: write the event to an `outbox` table in the same database transaction as the state change, then a relay process publishes to the message broker. This prevents the dual-write problem (lost or phantom events).
21
+
22
+ **Eventual consistency** is non-negotiable in event-driven systems. Design for it: define lag budgets per data type, make all event consumers idempotent, and implement explicit conflict resolution rules.
23
+
24
+ **Event schemas are public API:** never remove fields, never change field meaning, add only optional fields for backward-compatible evolution, and introduce new event type versions for breaking changes.
25
+
26
+ ## Deep Guidance
27
+
28
+ ## Shared-Nothing Data Patterns
29
+
30
+ ### The Shared-Nothing Principle
31
+
32
+ Each service owns its data exclusively. No service reads another service's database directly. No two services write to the same table. This is the foundational rule of multi-service data architecture.
33
+
34
+ **What shared-nothing means in practice:**
35
+ - Service A's Postgres instance is not accessible to service B — different credentials, different connection strings, potentially different infrastructure.
36
+ - Service A's database schema is an implementation detail of service A. Other teams do not get schema access.
37
+ - Service A's ORM models and migration files are not importable from service B's codebase.
38
+
39
+ **What shared-nothing does NOT mean:**
40
+ - Services cannot share a physical database server (they can — using separate schemas or separate databases on the same instance, in a cost-conscious environment).
41
+ - Services cannot share a cache like Redis (they can, using separate key namespaces).
42
+ - Data cannot flow between services (it can — through APIs and events).
43
+
44
+ **Enforcing shared-nothing:**
45
+ - CI/CD linting rule: no cross-service ORM model imports.
46
+ - Database credentials are distributed per-service — the order service's DB password is never in the user service's environment.
47
+ - Integration tests verify that removing service B does not break service A's database operations (only service A's API calls to B would break).
48
+
49
+ ### Data Isolation Levels
50
+
51
+ From strongest to weakest isolation (choose based on team size, cost, and compliance requirements):
52
+
53
+ | Level | Isolation | Cost | Use When |
54
+ |---|---|---|---|
55
+ | Separate clusters | Fully independent DB instances | High | PCI/HIPAA compliance, dramatically different scaling, full blast-radius isolation |
56
+ | Separate databases | Same cluster, different databases | Medium | Strong isolation without full infrastructure cost |
57
+ | Separate schemas | Same database, different schemas | Low | Small teams, cost-sensitive, still provides logical separation |
58
+ | Same schema, table prefix | Weakest logical separation | Lowest | Not recommended — one migration can affect all services |
59
+
60
+ ### Table Ownership Mapping
61
+
62
+ Create an explicit ownership registry. Every table in your system must have one owning service. If a table doesn't have a clear owner, the boundary is drawn incorrectly.
63
+
64
+ ```yaml
65
+ # data-ownership.yaml — committed to the monorepo root
66
+ # Single source of truth for data ownership decisions
67
+
68
+ services:
69
+ user-service:
70
+ owns:
71
+ - users
72
+ - user_profiles
73
+ - email_verifications
74
+ - sessions
75
+ - password_reset_tokens
76
+ publishes_events:
77
+ - user.registered
78
+ - user.email_verified
79
+ - user.profile_updated
80
+ - user.deactivated
81
+ reads_from_apis:
82
+ - order-service: [GET /orders?userId=] # for user dashboard
83
+
84
+ order-service:
85
+ owns:
86
+ - orders
87
+ - order_items
88
+ - order_status_history
89
+ publishes_events:
90
+ - order.placed
91
+ - order.confirmed
92
+ - order.fulfilled
93
+ - order.cancelled
94
+ reads_from_apis:
95
+ - catalog-service: [GET /products/:id] # price/availability check at placement
96
+ - user-service: [GET /users/:id] # shipping address at placement
97
+
98
+ catalog-service:
99
+ owns:
100
+ - products
101
+ - product_variants
102
+ - categories
103
+ - pricing_rules
104
+ publishes_events:
105
+ - product.created
106
+ - product.price_changed
107
+ - product.discontinued
108
+ reads_from_apis: [] # catalog is a leaf service — no upstream dependencies
109
+
110
+ inventory-service:
111
+ owns:
112
+ - inventory_levels
113
+ - reservations
114
+ - warehouse_locations
115
+ publishes_events:
116
+ - inventory.reserved
117
+ - inventory.released
118
+ - inventory.low_stock
119
+ - inventory.out_of_stock
120
+ reads_from_apis:
121
+ - catalog-service: [GET /products/:id] # product metadata
122
+ ```
123
+
124
+ When you fill in this registry and find ambiguity — a table that "could" belong to two services — that ambiguity is a design signal. Either the table belongs to one service and the other service accesses it via API, or the services should be merged.
125
+
126
+ ## Event-Driven Data Sync Strategies
127
+
128
+ ### The Outbox Pattern
129
+
130
+ The most reliable way to publish events from a service: write the event to an `outbox` table in the same database transaction as the state change, then a background process reads the outbox and publishes to the message broker.
131
+
132
+ **Why outbox is necessary:**
133
+ Without it, you face the dual-write problem: write to DB, then publish to Kafka. If the publish fails after the DB write, the event is lost. If the DB write fails after the publish, the event is published for a change that never happened. The outbox solves this by making both the state change and the event record part of the same ACID transaction.
134
+
135
+ ```sql
136
+ -- Outbox table schema (per-service, in the service's own database)
137
+ CREATE TABLE outbox_events (
138
+ id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
139
+ aggregate_id UUID NOT NULL,
140
+ aggregate_type TEXT NOT NULL, -- e.g., 'Order', 'User'
141
+ event_type TEXT NOT NULL, -- e.g., 'order.placed'
142
+ payload JSONB NOT NULL,
143
+ metadata JSONB NOT NULL DEFAULT '{}',
144
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
145
+ published_at TIMESTAMPTZ, -- NULL = not yet published
146
+ failed_at TIMESTAMPTZ,
147
+ retry_count INT NOT NULL DEFAULT 0
148
+ );
149
+
150
+ CREATE INDEX outbox_events_unpublished_idx
151
+ ON outbox_events (created_at)
152
+ WHERE published_at IS NULL AND failed_at IS NULL;
153
+ ```
154
+
155
+ **Outbox publishing flow:**
156
+
157
+ ```
158
+ 1. Application transaction:
159
+ BEGIN;
160
+ UPDATE orders SET status = 'confirmed' WHERE id = $1;
161
+ INSERT INTO outbox_events (aggregate_id, aggregate_type, event_type, payload)
162
+ VALUES ($1, 'Order', 'order.confirmed', $2);
163
+ COMMIT;
164
+
165
+ 2. Outbox relay process (runs every 100ms):
166
+ SELECT * FROM outbox_events
167
+ WHERE published_at IS NULL AND failed_at IS NULL
168
+ ORDER BY created_at
169
+ LIMIT 100;
170
+
171
+ 3. For each event:
172
+ publish_to_kafka(event);
173
+ UPDATE outbox_events SET published_at = now() WHERE id = $event_id;
174
+ ```
175
+
176
+ **Trade-offs:**
177
+ - (+) Exactly-once event publishing tied to the state change. No lost or phantom events.
178
+ - (+) Events are ordered within an aggregate (sequential IDs, ordered by created_at).
179
+ - (+) The outbox is a queryable audit log of all published events.
180
+ - (-) Adds a background relay process to every service.
181
+ - (-) Outbox table grows without cleanup — add a retention policy (delete events older than N days after publication).
182
+ - (-) Slight latency: events are published within the relay poll interval, not instantaneously.
183
+
184
+ ### Event-Driven Projections (Read Replicas)
185
+
186
+ When service A frequently needs data owned by service B, rather than calling B's API on every request, service A subscribes to B's events and maintains a local projection (a read-optimized copy of the subset of B's data that A needs).
187
+
188
+ **Example: Order service needs user shipping addresses**
189
+
190
+ Without projection: every order placement triggers a synchronous call to user-service to fetch the address.
191
+
192
+ With projection: order-service subscribes to `user.profile_updated` events and maintains a local `user_shipping_addresses` table. Order placement reads from this local table — no synchronous dependency on user-service.
193
+
194
+ ```typescript
195
+ // Order service: event handler for user profile updates
196
+ async function handleUserProfileUpdated(event: UserProfileUpdatedEvent): Promise<void> {
197
+ const { userId, shippingAddress } = event.payload;
198
+
199
+ await db.query(`
200
+ INSERT INTO user_shipping_addresses (user_id, street, city, state, postal_code, country, updated_at)
201
+ VALUES ($1, $2, $3, $4, $5, $6, $7)
202
+ ON CONFLICT (user_id) DO UPDATE SET
203
+ street = EXCLUDED.street,
204
+ city = EXCLUDED.city,
205
+ state = EXCLUDED.state,
206
+ postal_code = EXCLUDED.postal_code,
207
+ country = EXCLUDED.country,
208
+ updated_at = EXCLUDED.updated_at
209
+ `, [
210
+ userId,
211
+ shippingAddress.street,
212
+ shippingAddress.city,
213
+ shippingAddress.state,
214
+ shippingAddress.postalCode,
215
+ shippingAddress.country,
216
+ new Date()
217
+ ]);
218
+ }
219
+ ```
220
+
221
+ **Trade-offs:**
222
+ - (+) Eliminates runtime dependency on user-service for order placement. Order service works even if user-service is down.
223
+ - (+) Local reads are faster than cross-service API calls.
224
+ - (-) The projection is eventually consistent — there's a lag between a user updating their address and the order service seeing it (usually milliseconds to seconds, but could be longer if the consumer lags).
225
+ - (-) Schema must be managed carefully: if user-service changes the event payload, order-service's handler must be updated.
226
+ - (-) The projection table needs to be bootstrapped when first deployed (snapshot + replay historical events, or call the API to seed initial data).
227
+
228
+ ### Event Schema Management
229
+
230
+ Events are the public API of a service. They must be versioned and evolved carefully.
231
+
232
+ **Event schema rules:**
233
+ 1. **Never remove fields from a published event.** Consumers may depend on any field.
234
+ 2. **Never change the meaning of an existing field.** Rename by adding a new field alongside the old one.
235
+ 3. **Adding optional fields is backward-compatible.** Consumers that don't know about the new field ignore it.
236
+ 4. **Introduce new event types for breaking changes.** Publish `order.placed.v2` alongside `order.placed.v1` during a migration window; deprecate v1 after all consumers are updated.
237
+
238
+ ```json
239
+ {
240
+ "$schema": "http://json-schema.org/draft-07/schema",
241
+ "title": "order.placed",
242
+ "description": "Published when a customer places a new order",
243
+ "version": "1.2.0",
244
+ "type": "object",
245
+ "required": ["eventId", "eventType", "timestamp", "aggregateId", "payload"],
246
+ "properties": {
247
+ "eventId": {
248
+ "type": "string",
249
+ "format": "uuid",
250
+ "description": "Unique event identifier — use for idempotency"
251
+ },
252
+ "eventType": {
253
+ "type": "string",
254
+ "const": "order.placed"
255
+ },
256
+ "timestamp": {
257
+ "type": "string",
258
+ "format": "date-time"
259
+ },
260
+ "aggregateId": {
261
+ "type": "string",
262
+ "format": "uuid",
263
+ "description": "The order ID"
264
+ },
265
+ "payload": {
266
+ "type": "object",
267
+ "required": ["customerId", "items", "totalAmountCents", "currency"],
268
+ "properties": {
269
+ "customerId": { "type": "string", "format": "uuid" },
270
+ "items": {
271
+ "type": "array",
272
+ "items": {
273
+ "type": "object",
274
+ "required": ["productId", "quantity", "unitPriceCents"],
275
+ "properties": {
276
+ "productId": { "type": "string" },
277
+ "quantity": { "type": "integer", "minimum": 1 },
278
+ "unitPriceCents": { "type": "integer", "minimum": 0 }
279
+ }
280
+ }
281
+ },
282
+ "totalAmountCents": { "type": "integer", "minimum": 0 },
283
+ "currency": { "type": "string", "pattern": "^[A-Z]{3}$" },
284
+ "shippingAddressId": {
285
+ "type": "string",
286
+ "format": "uuid",
287
+ "description": "Added in v1.1.0 — optional for backward compatibility"
288
+ }
289
+ }
290
+ }
291
+ }
292
+ }
293
+ ```
294
+
295
+ ## Cross-Service Query Patterns
296
+
297
+ ### API Composition
298
+
299
+ A gateway or BFF assembles responses from multiple services in parallel, then merges the results for the client.
300
+
301
+ **When to use:** Read-heavy endpoints that need data from multiple services for a single screen. The composition is done at the edge (gateway/BFF) rather than in any individual service.
302
+
303
+ **Implementation:**
304
+ ```
305
+ GET /api/v1/dashboard
306
+ → Parallel requests (fan-out):
307
+ order-service: GET /orders?userId=123&limit=5 → recent orders
308
+ user-service: GET /users/123 → user profile
309
+ catalog-service: GET /products?featured=true → featured products
310
+ → Merge results into single response
311
+ ← 200 OK { user: {...}, recentOrders: [...], featured: [...] }
312
+ ```
313
+
314
+ **Trade-offs:**
315
+ - (+) No data replication — always reads current data from each service.
316
+ - (+) Simple to implement at the BFF layer.
317
+ - (-) Response time = slowest service + network overhead.
318
+ - (-) If any upstream service fails, the composed response fails or degrades.
319
+ - (-) Doesn't scale to complex queries (filtering, sorting, pagination across services).
320
+
321
+ ### CQRS (Command Query Responsibility Segregation)
322
+
323
+ Separate the write model (commands that change state) from the read model (queries optimized for reads). The read model is a denormalized projection maintained by consuming events.
324
+
325
+ **CQRS in a multi-service context:**
326
+
327
+ The write side lives in service A (owns the data, enforces invariants). The read side is a separate projection — either in service A (separate read DB) or in a dedicated read service that consumes service A's events.
328
+
329
+ ```typescript
330
+ // CQRS read model: Search service consumes events to maintain a product search index
331
+
332
+ // Write side: catalog-service handles product mutations
333
+ // catalog-service publishes events: product.created, product.updated, product.discontinued
334
+
335
+ // Read side: search-service consumes events and maintains Elasticsearch index
336
+ class ProductSearchProjection {
337
+ async handleProductCreated(event: ProductCreatedEvent): Promise<void> {
338
+ await this.searchClient.index({
339
+ index: 'products',
340
+ id: event.payload.productId,
341
+ document: {
342
+ id: event.payload.productId,
343
+ name: event.payload.name,
344
+ description: event.payload.description,
345
+ price: event.payload.priceCents / 100,
346
+ category: event.payload.categorySlug,
347
+ tags: event.payload.tags,
348
+ inStock: true, // default; updated by inventory events
349
+ indexedAt: new Date().toISOString()
350
+ }
351
+ });
352
+ }
353
+
354
+ async handleInventoryOutOfStock(event: InventoryOutOfStockEvent): Promise<void> {
355
+ await this.searchClient.update({
356
+ index: 'products',
357
+ id: event.payload.productId,
358
+ doc: { inStock: false }
359
+ });
360
+ }
361
+ }
362
+
363
+ // Query side: users query search-service for product discovery
364
+ // GET /search?q=shoes&category=footwear&inStock=true
365
+ // → Elasticsearch query — fast, full-text, faceted — no joins needed
366
+ ```
367
+
368
+ **Trade-offs:**
369
+ - (+) Read models are optimized for their specific query patterns — no impedance mismatch.
370
+ - (+) Read and write sides scale independently.
371
+ - (+) Multiple read models can consume the same events for different query patterns.
372
+ - (-) Eventual consistency: writes appear in the read model after event propagation delay.
373
+ - (-) More moving parts: write DB + event bus + read DB + projection consumer.
374
+ - (-) Projection failures must be monitored and recovered (consumer lag, dead-letter events).
375
+
376
+ ### Saga Pattern for Cross-Service Writes
377
+
378
+ When a business transaction spans multiple services, coordinate it with a saga — a sequence of local transactions, each publishing events to trigger the next step. If any step fails, compensating transactions undo previous steps.
379
+
380
+ **Choreography saga (event-driven, no central coordinator):**
381
+ ```
382
+ 1. order-service: order.placed →
383
+ 2. inventory-service: inventory.reserved (or inventory.insufficient) →
384
+ 3. payment-service: payment.processed (or payment.failed) →
385
+ 4. order-service: order.confirmed
386
+
387
+ On failure at step 3 (payment failed):
388
+ payment.failed →
389
+ inventory-service: releases reservation (compensating transaction)
390
+ order-service: cancels order
391
+ ```
392
+
393
+ **Orchestration saga (central coordinator):**
394
+ ```
395
+ OrderSaga (coordinator):
396
+ 1. Command inventory-service: ReserveInventory
397
+ ← InventoryReserved or InventoryInsufficient
398
+ 2. If reserved: Command payment-service: ProcessPayment
399
+ ← PaymentProcessed or PaymentFailed
400
+ 3. If paid: Command order-service: ConfirmOrder
401
+ ← OrderConfirmed
402
+ On failure: issue compensating commands in reverse order
403
+ ```
404
+
405
+ **Trade-offs of choreography:**
406
+ - (+) No central coordinator — services are decoupled.
407
+ - (-) Saga flow is distributed across service event handlers — hard to visualize and debug.
408
+ - (-) Adding a new step requires changing multiple services.
409
+
410
+ **Trade-offs of orchestration:**
411
+ - (+) Saga flow is in one place — the orchestrator. Easier to reason about.
412
+ - (+) Easier to add steps, error handling, and timeouts.
413
+ - (-) Orchestrator service is a potential bottleneck and single point of failure.
414
+ - (-) Orchestrator must be highly available and idempotent.
415
+
416
+ **Rule:** Use choreography for simple 2-3 step flows. Use orchestration for flows with 4+ steps, complex branching, or strict timeout requirements.
417
+
418
+ ## Eventual Consistency Handling
419
+
420
+ ### Designing for Lag
421
+
422
+ Eventual consistency means there is a window of time during which different services have different views of the same fact. Design the user experience and business logic to tolerate this lag.
423
+
424
+ **Lag budgets:** Define the acceptable consistency window for each data flow.
425
+
426
+ | Data Type | Acceptable Lag | Pattern |
427
+ |---|---|---|
428
+ | Shopping cart → order totals | < 100ms | Synchronous API call |
429
+ | Product price display | < 5 minutes | Event-driven projection with short TTL |
430
+ | Inventory count display | < 1 minute | Event-driven projection |
431
+ | User activity feed | < 10 minutes | CQRS read model |
432
+ | Analytics dashboard | Hours | Batch ETL acceptable |
433
+
434
+ ### Idempotency
435
+
436
+ Every event consumer and API mutation must be idempotent — processing the same event twice produces the same result as processing it once. This is non-negotiable in eventual consistency systems because at-least-once delivery means duplicate delivery is guaranteed to happen.
437
+
438
+ **Idempotency implementation patterns:**
439
+
440
+ 1. **Natural idempotency:** The operation is inherently idempotent. `UPDATE users SET email = $1 WHERE id = $2` — running it 10 times produces the same result as running it once.
441
+
442
+ 2. **Idempotency key table:** For operations that are not naturally idempotent (e.g., sending an email, charging a card), record the idempotency key and skip reprocessing if already done.
443
+
444
+ ```sql
445
+ CREATE TABLE processed_events (
446
+ event_id UUID PRIMARY KEY,
447
+ event_type TEXT NOT NULL,
448
+ processed_at TIMESTAMPTZ NOT NULL DEFAULT now()
449
+ );
450
+
451
+ -- In the event handler:
452
+ BEGIN;
453
+ INSERT INTO processed_events (event_id, event_type)
454
+ VALUES ($eventId, $eventType)
455
+ ON CONFLICT (event_id) DO NOTHING;
456
+
457
+ -- Check if the insert was a no-op (already processed)
458
+ GET DIAGNOSTICS rows_affected = ROW_COUNT;
459
+ IF rows_affected = 0 THEN
460
+ ROLLBACK;
461
+ RETURN; -- Skip duplicate
462
+ END IF;
463
+
464
+ -- Perform the actual work
465
+ -- ...
466
+ COMMIT;
467
+ ```
468
+
469
+ 3. **Optimistic locking with version:** For state machines (order status transitions), only apply the transition if the entity's current version matches what the event was generated against.
470
+
471
+ ### Conflict Resolution
472
+
473
+ When the same data can be modified by multiple services or clients concurrently, define explicit conflict resolution rules.
474
+
475
+ **Last-writer-wins (LWW):** The update with the most recent timestamp wins. Simple but can silently discard updates.
476
+
477
+ **Vector clocks:** Track causality across distributed writes. Complex to implement but preserves all updates.
478
+
479
+ **Domain-specific merge:** Inventory counts use addition/subtraction rather than SET — `UPDATE inventory SET quantity = quantity - $reserved WHERE product_id = $1` is conflict-safe in ways that `UPDATE inventory SET quantity = $new_quantity WHERE product_id = $1` is not.
480
+
481
+ ## Data Migration Between Services
482
+
483
+ ### When to Migrate Data Between Services
484
+
485
+ Service boundary changes are expensive but sometimes necessary:
486
+
487
+ - A table that belongs to two services (wrong boundary) needs to be moved to one.
488
+ - A service is being split (feature extraction) and some tables belong to the new service.
489
+ - A service is being merged (two services are always deployed together) and their data stores need to be consolidated.
490
+
491
+ ### Migration Strategy: Parallel Write, Incremental Cutover
492
+
493
+ Never do a big-bang migration. Use incremental cutover with a parallel write phase.
494
+
495
+ **Phase 1: Dual write.** The owning service writes to both the old location and the new location simultaneously. Reads still go to the old location. This establishes confidence that the new location is getting complete data.
496
+
497
+ **Phase 2: Backfill.** Migrate historical data from the old location to the new location. Validate completeness (row counts, checksums on key fields).
498
+
499
+ **Phase 3: Read switchover.** Switch reads to the new location. Keep dual writes active. Monitor for errors.
500
+
501
+ **Phase 4: Stop old writes.** Remove the write to the old location. The new location is now authoritative.
502
+
503
+ **Phase 5: Cleanup.** After a validation period, delete the old table/schema and remove any migration scaffolding.
504
+
505
+ ```
506
+ Timeline:
507
+ Day 0: Deploy dual-write. New writes go to both old and new DB.
508
+ Day 1: Run backfill job for historical data.
509
+ Day 2: Validate: compare row counts and sample records.
510
+ Day 3: Switch reads to new DB. Monitor error rates.
511
+ Day 7: Stop writes to old DB. New DB is authoritative.
512
+ Day 14: Delete old table. Remove migration code.
513
+ ```
514
+
515
+ ### Data Partitioning for Tenancy
516
+
517
+ In multi-tenant systems, data can be partitioned at different levels:
518
+
519
+ **Schema-per-tenant:** Each tenant gets their own schema (within a shared database). Strong isolation for compliance, simple to backup/restore per tenant. Operational overhead scales with tenant count.
520
+
521
+ **Row-level tenant isolation:** All tenants share tables; every table has a `tenant_id` column. Row-level security (Postgres RLS) enforces isolation in the database. Simple to operate but cross-tenant isolation depends on correct query predicates.
522
+
523
+ **Database-per-tenant (silo model):** Maximum isolation. Required for compliance contexts (healthcare, finance). Highest operational cost — N databases to manage.
524
+
525
+ **Rule:** Start with row-level isolation (simplest to operate). Move to schema-per-tenant if a tenant's data volume or compliance requirements demand it. Move to database-per-tenant only for enterprise/regulated customers.
526
+
527
+ ## Common Pitfalls
528
+
529
+ **Sharing database credentials across services.** Service A should not have connection credentials for service B's database. This is the shared-nothing violation most commonly enforced informally rather than technically. Fix: use a secrets manager (Vault, AWS Secrets Manager) that distributes credentials per-service, never in shared environment files.
530
+
531
+ **Missing idempotency on event consumers.** "We have at-least-once delivery, but we've designed consumers assuming exactly-once." This causes duplicate emails, double charges, and inventory over-releases. Fix: idempotency keys table in every event consumer before any non-idempotent side effect.
532
+
533
+ **Projections without bootstrap strategy.** A new service subscribes to events, but there are two years of historical events. The projection is empty or stale on first deploy. Fix: every projection service must have a documented bootstrap procedure (API snapshot + event replay from snapshot timestamp, or bulk seed via API).
534
+
535
+ **Saga without compensating transactions.** Starting a saga without defining what happens when step N fails. Partial state changes across services become orphaned. Fix: before implementing any saga, write out the full compensation matrix — for each step, what is the compensating transaction?
536
+
537
+ **Treating eventual consistency as a synchronization problem.** Trying to make an eventually consistent system look strongly consistent by adding polling, timeouts, and retry loops at the API layer. This adds latency and complexity without fixing the root issue. Fix: design the UX to reflect consistency guarantees — optimistic UI updates with reconciliation, not blocking spinners waiting for consistency.
538
+
539
+ **Over-normalizing event payloads.** Publishing events with minimal data (just an ID) and requiring consumers to call back to the owning service to get details. This creates temporal coupling — if the owning service is unavailable, the consumer can't process the event. Fix: events should be self-contained — include all data the consumer needs to process the event without additional API calls.