@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
package/README.md CHANGED
@@ -389,6 +389,7 @@ Every `scaffold init` wizard question can be answered via CLI flags, making scaf
389
389
  | `--backend-auth` | string | none, jwt, session, oauth, apikey |
390
390
  | `--backend-messaging` | string | none, queue, event-driven |
391
391
  | `--backend-deploy-target` | string | serverless, container, long-running |
392
+ | `--backend-domain` | string | none, fintech |
392
393
 
393
394
  #### CLI Config Flags (require `--project-type cli` or auto-set it)
394
395
 
@@ -472,6 +473,33 @@ Every `scaffold init` wizard question can be answered via CLI flags, making scaf
472
473
 
473
474
  > **Flag aliases**: Game flags have `--game-*` aliases for consistency with other project types (e.g., `--game-engine` is equivalent to `--engine`). Bare flags like `--engine` still work.
474
475
 
476
+ ### Declarative init from a YAML manifest (`--from`)
477
+
478
+ For multi-service projects, use `scaffold init --from <file>` to provide a
479
+ full ScaffoldConfig as YAML instead of running the interactive wizard:
480
+
481
+ ```bash
482
+ scaffold init --from services.yml --force
483
+ ```
484
+
485
+ The file must be a complete ScaffoldConfig (with `version`, `methodology`,
486
+ `platforms`, and `project.services[]`). Pass `-` to read from stdin.
487
+
488
+ `--from` is exclusive with config-setting flags (`--methodology`, all
489
+ `--backend-*`, `--web-*`, etc.); combining them is an error. Operational
490
+ flags (`--root`, `--force`, `--auto`, `--verbose`, `--format`) still work.
491
+
492
+ **Multi-service execution (v3.17.0+)**: Multi-service projects are
493
+ fully executable. Every stateful command (`run`, `next`, `status`,
494
+ `skip`, `complete`, `info`, `dashboard`, `decisions`, `reset`,
495
+ `rework`) accepts `--service <name>` to target one service's pipeline.
496
+ State is sharded under `.scaffold/services/<name>/state.json` with a
497
+ merged global+service view; per-service locks are independent.
498
+ Services can expose artifacts for cross-service consumption via the
499
+ `exports` allowlist in config, and pipeline steps can declare
500
+ `cross-reads:` in their frontmatter to consume foreign artifacts
501
+ during assembly.
502
+
475
503
  #### How Flags Interact
476
504
 
477
505
  - **Flag > auto > interactive**: Flags always take highest precedence. `--auto --engine unreal` uses defaults for everything except engine.
@@ -0,0 +1,244 @@
1
+ ---
2
+ name: backend-fintech-broker-integration
3
+ description: Multi-broker adapter pattern; credential rotation; error harmonization; rate-limit management; broker-side quirks.
4
+ topics: [backend, fintech, brokers, integration, adapter-pattern, rate-limits, credentials, retry]
5
+ ---
6
+
7
+ A fintech backend that routes orders or reads positions across more than one broker inherits the union of every broker's quirks, outages, auth schemes, and undocumented behaviors. The broker-integration layer exists to hide that mess behind one normalized internal contract so the rest of the system — risk, order lifecycle, ledger, UI — can stay clean. This doc covers the adapter contract, credential handling, error harmonization, rate-limit strategy, and the specific pitfalls that recur regardless of which brokers you connect.
8
+
9
+ ## Summary
10
+
11
+ Brokers do not share conventions. Alpaca exposes REST+WebSocket with OAuth or static keys; Interactive Brokers' Client Portal uses a local gateway with session tokens that time out; Tradier is REST-only with bearer tokens; TD/Schwab uses OAuth with mandatory refresh flows; institutional venues speak FIX 4.2/4.4 over persistent TCP; some prime brokers ship vendor-specific binary protocols. Auth differs. Order-state taxonomies differ (Alpaca's `new/partially_filled/filled/canceled/expired` does not map 1:1 to IBKR's `Submitted/PreSubmitted/Cancelled/Filled`). Error-code schemes differ. Rate-limit headers differ — when they exist at all. Building "just one more broker-specific branch" into the order service is how a codebase becomes unmaintainable in 18 months. The adapter layer is the discipline that prevents that.
12
+
13
+ The adapter contract is a narrow, normalized internal API: `placeOrder`, `cancelOrder`, `replaceOrder`, `fetchOrder`, `fetchFills`, `fetchPositions`, `fetchBalance`, `subscribeFills`, `subscribeQuotes`. Each broker-specific adapter implements that interface; everything above the interface — risk checks, order router, position service — speaks only the normalized types. Define the contract in an IDL (protobuf, OpenAPI, or just a well-typed TS/Go module) so schema drift is a compile error, not a runtime surprise. Normalized types include a canonical `OrderStatus` enum, a canonical `RejectReason` enum, and monetary fields in minor units (see `backend-fintech-ledger.md`).
14
+
15
+ Credentials never live in source, never in plain env vars in production, and never in the container image. Store them in a secrets manager with audit trails (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault, 1Password Service Accounts). Fetch at process start, cache in memory with a TTL, refresh on expiry. Rotate on a schedule (30–90 days for static keys) and rotate immediately on any suspected compromise, employee offboarding, or vendor breach. Broker OAuth refresh tokens require a separate refresh worker — long-running processes with stale tokens are the single most common broker outage cause in practice.
16
+
17
+ Error harmonization converts broker-specific failures into a small internal taxonomy the rest of the system can reason about: `transient` (retry with exponential backoff + jitter), `rate_limited` (backoff + queue, honor any `Retry-After`), `invalid` (non-retriable; surface to user with a clean reason), `auth` (refresh credentials and retry once; alert on repeat), `outage` (circuit-break, disable new-order flow, alert on-call), `unknown` (state is indeterminate — do NOT blindly retry; queue for reconciliation — see `backend-fintech-order-lifecycle.md`). The mapping table is the most audited piece of the adapter; mis-classifying `unknown` as `transient` causes duplicate orders and customer-visible incidents.
18
+
19
+ Rate-limit management is client-side: a token-bucket per broker per credential (plus per-endpoint where brokers split limits), sized below the broker's published limit to leave headroom for retries. Back-pressure upstream — the order router should block on an awaitable token, not spin-retry — so quota exhaustion manifests as latency, not cascading rejections. On approaching the limit, prioritize cancellations over new orders (cancellations reduce risk; new orders increase it). See `backend-fintech-risk-management.md` for why that ordering matters during a volatility event.
20
+
21
+ ## Deep Guidance
22
+
23
+ ### Typical Broker APIs and Their Tradeoffs
24
+
25
+ Retail/prosumer brokers mostly ship **REST+WebSocket**: REST for order entry and account state, WebSocket for fills, quotes, and order updates. Alpaca, Tradier, Tastytrade, Robinhood (unofficial), and TD Ameritrade/Schwab follow this shape. Latency is 50–500ms per REST call, quotes arrive via WebSocket in tens of ms. Rate limits are HTTP-header-driven (`X-RateLimit-Remaining`, `Retry-After`) and typically 200–1000 requests/minute per key.
26
+
27
+ **Interactive Brokers** is its own category. The Client Portal Gateway runs locally (or in a sidecar container), maintains a session, and exposes REST on localhost. Sessions expire (roughly every 24h) and require re-auth. IBKR also offers TWS API (native C++/Java/Python bindings over a local socket) and FIX for institutional. The gateway model forces you to treat the broker connection as a stateful component with its own health check and restart policy.
28
+
29
+ **FIX protocol** (Financial Information eXchange, 4.2 or 4.4 most common) is the institutional standard: persistent TCP session with sequence numbers, heartbeats, and gap-fill recovery. Latency is sub-millisecond but the engineering burden is real: FIX engines (QuickFIX/n, QuickFIX/J, OnixS) require session-state management, store-and-forward semantics, and careful handling of re-sends after disconnect. Use a battle-tested FIX engine; do not roll your own.
30
+
31
+ **Vendor binary protocols** (some prime brokers, ATS venues) are a further step down the latency/engineering tradeoff — only worth the cost when microseconds matter. For most fintech backends, REST+WS is the right default; FIX is added when institutional routing or smart-order-routing vendors (Trading Technologies, ION, Fidessa) require it.
32
+
33
+ Reliability and cost also vary. Alpaca's free tier has generous throughput but less uptime history; IBKR has strong reliability but complex gateway management; TD/Schwab is reliable but OAuth is fiddly and the API has frequent deprecations. Track each broker's actual availability via your own monitors — do not trust the status page.
34
+
35
+ ### Authentication Patterns
36
+
37
+ - **Static API keys (HMAC-signed).** Coinbase, Kraken, older Alpaca. Secret is used to sign a canonical request string (method + path + timestamp + body) with HMAC-SHA256; server verifies. Safer than bearer tokens because the secret never crosses the wire. Rotate quarterly via zero-downtime dual-key windows (new key active; old key accepts for 24h; cut over; revoke old).
38
+ - **Bearer tokens (static).** Tradier, older Alpaca. Simpler; leaked token is used verbatim. Rotate monthly; prefer HMAC when the broker supports both.
39
+ - **OAuth 2.0 with refresh.** TD/Schwab, Robinhood (unofficial), many brokerage aggregators (Plaid Investments, SnapTrade). Access token TTL 15–60 minutes; refresh token TTL days-to-months. Requires a dedicated refresh worker that renews tokens *before* expiry (30–60s buffer) and persists the new access/refresh pair atomically. Refresh-token rotation (where the server issues a new refresh token each time) is common and forces strict single-consumer semantics — only one process may refresh at a time; use a distributed lock (Redis, Postgres advisory lock).
40
+ - **Session-based.** IBKR Client Portal. Login once, keep-alive periodically, reauth on 401. The gateway is stateful; treat it as a singleton per account and health-check the session, not just the gateway process.
41
+ - **mTLS or client certificates.** Institutional FIX sessions and some bank APIs. Certificates rotate annually; automate issuance via your PKI (cert-manager + Vault PKI, AWS Private CA).
42
+
43
+ Never check secrets into Git. Never pass them on the CLI (they land in shell history and `ps`). Mount them as in-memory files or fetch from the secrets manager at process start. Scrub from logs by field name and by regex (common token shapes: `sk_live_*`, `AK*`, JWT `eyJ*`). Audit-log every credential read with actor id and reason — this is a SOC 2 CC6 control (see `backend-fintech-compliance.md`).
44
+
45
+ ### Adapter Interface Design
46
+
47
+ Define the contract once, in one place. Below is the normalized TypeScript interface; the same shape translates to Go interfaces, Python Protocols, or protobuf services.
48
+
49
+ ```typescript
50
+ // Normalized broker adapter contract. Every broker-specific class implements
51
+ // this interface; all callers depend on the interface, never on a concrete
52
+ // broker type.
53
+
54
+ export type OrderSide = 'buy' | 'sell';
55
+ export type OrderType = 'market' | 'limit' | 'stop' | 'stop_limit';
56
+ export type TimeInForce = 'day' | 'gtc' | 'ioc' | 'fok';
57
+ export type OrderStatus =
58
+ | 'pending_new' // submitted to us, not yet acked by broker
59
+ | 'new' // acked by broker, working
60
+ | 'partially_filled'
61
+ | 'filled'
62
+ | 'canceled'
63
+ | 'rejected'
64
+ | 'expired'
65
+ | 'unknown'; // reconciliation required
66
+
67
+ export interface PlaceOrderRequest {
68
+ clientOrderId: string; // UUID — idempotency key across retries
69
+ accountId: string;
70
+ symbol: string; // normalized ticker (e.g. 'AAPL')
71
+ side: OrderSide;
72
+ type: OrderType;
73
+ quantity: bigint; // minor units or share count, per asset class
74
+ limitPrice?: bigint; // minor units
75
+ stopPrice?: bigint;
76
+ timeInForce: TimeInForce;
77
+ submittedAt: Date;
78
+ }
79
+
80
+ export interface OrderAck {
81
+ clientOrderId: string;
82
+ brokerOrderId: string;
83
+ status: OrderStatus;
84
+ acceptedAt: Date;
85
+ }
86
+
87
+ export interface BrokerAdapter {
88
+ placeOrder(req: PlaceOrderRequest): Promise<OrderAck>;
89
+ cancelOrder(clientOrderId: string): Promise<void>;
90
+ replaceOrder(clientOrderId: string, changes: Partial<PlaceOrderRequest>): Promise<OrderAck>;
91
+ fetchOrder(clientOrderId: string): Promise<OrderState>;
92
+ fetchFills(since: Date): Promise<Fill[]>;
93
+ fetchPositions(accountId: string): Promise<Position[]>;
94
+ fetchBalance(accountId: string): Promise<Balance>;
95
+ subscribeFills(onFill: (f: Fill) => void): Promise<Subscription>;
96
+ subscribeQuotes(symbols: string[], onQuote: (q: Quote) => void): Promise<Subscription>;
97
+ healthCheck(): Promise<HealthStatus>;
98
+ }
99
+ ```
100
+
101
+ Keep the interface narrow. Broker-specific features (IBKR's advanced order types, Alpaca's fractional shares, crypto venues' margin calls) either fit into optional fields on the normalized types or require a capability flag (`adapter.supports('fractional_shares')`). Resist the temptation to leak broker-specific escape hatches; the moment one caller reaches past the interface, the abstraction stops paying rent.
102
+
103
+ Version the IDL. Brokers change payloads without notice; when they do, the adapter absorbs it and the interface stays stable — but when *your* normalized shape needs to change (new order type, new status), bump the interface version and support both for a deprecation window.
104
+
105
+ ### Error Harmonization Taxonomy
106
+
107
+ Every adapter maps broker responses into the internal taxonomy. Get this mapping wrong and you either retry into duplicate orders (worst case) or alert on every transient blip (operational noise). Below is the canonical mapping pattern.
108
+
109
+ ```typescript
110
+ type InternalErrorClass =
111
+ | 'transient' // network flake, 5xx; retry with backoff+jitter
112
+ | 'rate_limited' // 429; backoff + queue; honor Retry-After
113
+ | 'invalid' // 4xx; never retry; surface to user
114
+ | 'auth' // 401/403; refresh credential, retry once; alert on repeat
115
+ | 'outage' // broker-wide failure; circuit-break; alert on-call
116
+ | 'unknown'; // indeterminate state; reconcile, do NOT retry
117
+
118
+ // Alpaca → internal (illustrative).
119
+ // Order matters: ambiguous/specific cases must match BEFORE the generic 5xx
120
+ // branch or a timeout on a mutating call would be silently retried as
121
+ // `transient` and duplicate orders. Split idempotent reads from mutations
122
+ // at the call site so a read-path 500 can still retry freely.
123
+ function classifyAlpacaError(
124
+ resp: AlpacaErrorResponse,
125
+ op: 'read' | 'mutate',
126
+ ): InternalErrorClass {
127
+ if (resp.httpStatus === 429) return 'rate_limited';
128
+ if (resp.httpStatus === 401 || resp.httpStatus === 403) return 'auth';
129
+ if (resp.httpStatus === 422 && resp.code === 40010001) return 'invalid'; // insufficient buying power
130
+ if (resp.httpStatus === 422 && resp.code === 40310000) return 'invalid'; // market closed
131
+ // Specific 5xx shapes first:
132
+ if (resp.httpStatus === 503 && /maintenance/i.test(resp.message)) return 'outage';
133
+ if (resp.httpStatus === 504 || resp.code === 'timeout') {
134
+ // Mutating call + indeterminate response = reconcile, never blind-retry.
135
+ return op === 'mutate' ? 'unknown' : 'transient';
136
+ }
137
+ // Generic 5xx last. A 500 on a mutation is STILL ambiguous — bubble as
138
+ // `unknown` so the order-lifecycle reconciler resolves it.
139
+ if (resp.httpStatus >= 500) return op === 'mutate' ? 'unknown' : 'transient';
140
+ return 'unknown';
141
+ }
142
+ ```
143
+
144
+ The critical distinction is `transient` vs `unknown`. A 500 on a read (fetch positions) is `transient` — retry freely, the read is idempotent. A 500 or timeout on a `placeOrder` call is `unknown` — the order may or may not have reached the broker's matching engine. Retrying risks a duplicate fill; not reconciling risks a missed fill. The correct handling is to enqueue a reconciliation job that polls the broker by `clientOrderId` (which was sent with the request) until the order's fate is determined, then emit the appropriate state transition. This is why `clientOrderId` is mandatory on every place call, not optional. Cross-ref `backend-fintech-order-lifecycle.md` for the full reconciliation flow.
145
+
146
+ Maintain the error mapping per-broker in a table that product owners and on-call engineers can read without spelunking through code. When the broker ships new error codes (they will, without notice), the table is what gets updated.
147
+
148
+ ### Idempotency With Brokers
149
+
150
+ Every broker worth integrating accepts a client-supplied order id (`client_order_id`, `clOrdID` in FIX, `externalOrderId`, naming varies). Always send one, always make it a UUID v4 you generate before the first attempt, always persist it before the network call. This gives you two properties: (a) retries dedupe at the broker — a second `placeOrder` with the same `clientOrderId` returns the existing order, not a new one; (b) reconciliation has a stable key to look up on `fetchOrder(clientOrderId)`.
151
+
152
+ A few brokers reject a second attempt with the same `clientOrderId` even when the first was rate-limited or dropped mid-flight. Your adapter must treat that rejection specifically — it is an *expected* outcome of the retry, not an error. Map it to "fetch the existing order and return the canonical `OrderAck`."
153
+
154
+ For brokers that do not support client-order-id (rare, mostly legacy or crypto venues), substitute a deduplication window at the adapter: persist `(account, symbol, side, quantity, limit_price, submitted_within_N_seconds)` and reject duplicates before hitting the network. This is weaker than a real idempotency key; flag the broker as a migration priority.
155
+
156
+ ### Broker-Outage Handling
157
+
158
+ Brokers go down. The 2021 Robinhood and Schwab outages cost real customer money; IBKR's quarterly maintenance windows are announced but often overrun; exchanges halt for SSR or volatility circuit breakers. The adapter must detect and react.
159
+
160
+ **Circuit breaker per broker per endpoint** (libraries: `opossum` in Node, Resilience4j in Java, `gobreaker` in Go). Trip threshold: e.g. 50% failure rate over 20-call rolling window or 10 consecutive `outage`/`transient` errors. Open state rejects calls immediately with a cached "broker unavailable" response. Half-open probes one request every 30s; success closes the breaker.
161
+
162
+ Graceful degradation while open: disable new-order submission, continue to allow cancellations (cancellations reduce exposure — prioritize the path that shrinks risk). Continue to serve last-known positions/balances from cache with a clear staleness marker. Page on-call within minutes, not hours — for a brokerage, "we can't place orders" is a customer-impact P0.
163
+
164
+ User communication: the UI must distinguish "your order failed because of you" from "your order failed because of us." A generic "try again later" is worse than useless during an outage; it creates support load. Publish a status endpoint that the UI reads and surface a specific broker banner.
165
+
166
+ Record every circuit-breaker state change to the audit log (see `backend-fintech-compliance.md`) — regulators and customers will ask for a postmortem timeline.
167
+
168
+ ### Rate-Limit Strategy: Token Bucket
169
+
170
+ ```typescript
171
+ // Client-side token bucket. One per (broker, credential, endpoint-group).
172
+ // Size the rate below the broker's published limit — leave headroom for
173
+ // retries and for cross-shard contention if multiple workers share credentials.
174
+
175
+ class TokenBucket {
176
+ private tokens: number;
177
+ private lastRefill: number;
178
+
179
+ constructor(
180
+ private readonly capacity: number, // burst size
181
+ private readonly refillPerSecond: number // sustained rate
182
+ ) {
183
+ this.tokens = capacity;
184
+ this.lastRefill = Date.now();
185
+ }
186
+
187
+ // Await a token. Resolves when one is available; rejects on timeout.
188
+ async take(timeoutMs: number = 30_000): Promise<void> {
189
+ const deadline = Date.now() + timeoutMs;
190
+ while (true) {
191
+ this.refill();
192
+ if (this.tokens >= 1) {
193
+ this.tokens -= 1;
194
+ return;
195
+ }
196
+ const waitMs = Math.min(
197
+ (1 / this.refillPerSecond) * 1000,
198
+ Math.max(0, deadline - Date.now())
199
+ );
200
+ if (waitMs <= 0) throw new Error('rate-limit wait timed out');
201
+ await new Promise((r) => setTimeout(r, waitMs));
202
+ }
203
+ }
204
+
205
+ private refill(): void {
206
+ const now = Date.now();
207
+ const elapsedSec = (now - this.lastRefill) / 1000;
208
+ this.tokens = Math.min(
209
+ this.capacity,
210
+ this.tokens + elapsedSec * this.refillPerSecond
211
+ );
212
+ this.lastRefill = now;
213
+ }
214
+ }
215
+
216
+ // Usage: await bucket.take() before every broker call. Exhaustion manifests
217
+ // as latency, not errors — upstream request handlers block on the bucket.
218
+ ```
219
+
220
+ For multi-process/multi-region deployments sharing the same credential, a distributed rate-limiter (Redis with Lua scripts, or a dedicated service) replaces the in-memory bucket. Honor the broker's `Retry-After` header as an override — if the broker says "wait 5s," pause the bucket for 5s regardless of its own refill state.
221
+
222
+ Prioritize traffic when approaching the limit: cancellations before new orders; reads before writes; interactive user requests before background reconciliation jobs. A priority queue in front of the bucket expresses this directly.
223
+
224
+ ### Testing Strategy
225
+
226
+ - **Sandbox environments.** Alpaca, IBKR, Tradier, and most modern brokers provide paper/sandbox endpoints. Use them for integration tests in CI — not all brokers have them, and sandboxes diverge from production in subtle ways (sandbox may accept orders production would reject for PDT rules, fractional-share limits, etc.). Treat sandbox-green as necessary but not sufficient.
227
+ - **Record/replay for unit tests.** Capture real broker responses (with secrets scrubbed) into fixtures (`nock`, `vcrpy`, `go-vcr`). Unit tests run fully offline and deterministically. Refresh fixtures quarterly and after any broker API version bump.
228
+ - **Contract tests on schedule.** A cron job (hourly or daily) that exercises the full adapter contract against sandbox — place, cancel, fetch, subscribe — and alerts on any behavior drift. Brokers ship breaking changes without notice; contract tests catch them before customers do.
229
+ - **Chaos testing.** Inject rate-limit responses, 5xx, timeouts, and partial payloads into the adapter's HTTP layer and assert the error taxonomy classifies correctly. Tools: `toxiproxy`, `wiremock`, a local test double that replays broker responses.
230
+ - **Reconciliation backfill tests.** Simulate an "unknown" outcome and assert the reconciler converges to the correct terminal state by polling `fetchOrder`. This is where bugs hide — exercise it deliberately.
231
+
232
+ ### Common Pitfalls
233
+
234
+ - **Retry storms amplifying rate limits.** A broker blip returns 500s; naive retry immediately doubles the traffic, tripping 429s, which retry further. Always use exponential backoff with full jitter (`delay = random(0, base * 2^attempt)`, capped) and a max-attempts limit. Cross-instance coordination via a shared token bucket prevents fleet-wide pile-ons.
235
+ - **Stale auth tokens on long-running processes.** Workers started at 9am with a 60-minute access token stop working at 10am. Refresh proactively with a 30–60s buffer before expiry; run the refresh on a dedicated timer per credential, not inline per request. Persist the refreshed token atomically — two workers refreshing concurrently with a rotating refresh-token scheme will invalidate each other.
236
+ - **Time-zone bugs around market hours.** US equities open 9:30am ET, not 9:30am local. Always store and compute in UTC with an explicit exchange-calendar library (`pandas_market_calendars`, `exchange_calendars`, `NYSE holidays`). Half-days (day after Thanksgiving, Christmas Eve) close at 1:00pm ET — miss these and orders reject late in the day.
237
+ - **Order-state drift when webhooks are missed.** WebSocket disconnects and misses fill events; the internal state lags the broker. Run a periodic `fetchOrder` + `fetchFills` reconciliation (every 30–60s for active orders) even when the WS is healthy. Belt and suspenders — do not trust a single channel.
238
+ - **Assuming broker order-id uniqueness across accounts.** Some brokers scope order ids per account, not globally. Always join on `(broker, account, brokerOrderId)` in your own storage. Your `clientOrderId` is globally unique; the broker's id may not be.
239
+ - **Blindly trusting broker timestamps.** Broker clocks drift. Persist both the broker's timestamp and your own received-at timestamp; reconcile against trading day boundaries using exchange calendars, not broker-reported times.
240
+ - **Silent symbol normalization drift.** `BRK.B` vs `BRK/B` vs `BRK-B` — brokers disagree on tickers with punctuation. Normalize on ingress and egress; keep a per-broker mapping table. Same for options (OCC symbology vs broker-specific shorthand).
241
+ - **Shared credentials across environments.** A staging deploy pointing at production credentials places real orders. Enforce via separate secrets per environment, network-level allowlists, and a pre-flight check that refuses to start if the environment tag does not match the credential's expected tag.
242
+ - **Not testing the sad path under load.** Everything works at 10 req/min in staging; at 500 req/min in production the bucket empties, retries pile up, and the circuit breaker oscillates. Load-test the adapter with realistic traffic shapes including error injection.
243
+
244
+ See also `backend-fintech-order-lifecycle.md` for the order state machine and reconciliation flows that depend on the adapter's `unknown` classification, `backend-fintech-risk-management.md` for how rate-limit prioritization interacts with kill-switch logic, `backend-fintech-observability.md` for the metrics and traces every adapter should emit, and `backend-fintech-compliance.md` for credential-audit and change-management expectations.
@@ -0,0 +1,181 @@
1
+ ---
2
+ name: backend-fintech-compliance
3
+ description: PCI-DSS, SOC 2, SEC/FINRA regulations for consumer/B2B fintech backends; audit trail immutability; data retention; segregation of duties.
4
+ topics: [backend, fintech, compliance, pci-dss, soc2, sec, finra, audit-trail, gdpr]
5
+ ---
6
+
7
+ Fintech compliance is not a checklist applied at the end — it determines schema design, deployment pipelines, and system boundaries. Most regulations apply based on what a service *touches* (cards, trades, PII), so scope reduction is the single highest-leverage design decision available to engineering. This doc covers the regulatory regimes a typical US/EU fintech encounters, the audit-trail patterns they demand, and concrete implementation choices that keep audits survivable.
8
+
9
+ ## Summary
10
+
11
+ Which regulations apply depends on what the service handles. Handling card data (PAN, CVV, track data) triggers PCI-DSS v4.0. Storing customer PII, financial records, or operating as a service provider to regulated firms triggers SOC 2 Type II expectations from customers and GLBA obligations (US financial privacy). Executing securities trades or routing orders triggers SEC Rule 17a-4 record retention and FINRA supervisory requirements. Operating in the EU triggers GDPR and — for crypto — MiCA. Serving retail vs institutional customers changes consumer-protection obligations (Reg Z, Reg E, CFPB oversight vs institutional carve-outs).
12
+
13
+ Compliance cost scales with scope, not with traffic. A service that never sees raw PANs is *out of scope* for most PCI-DSS controls. A microservice that only handles trade metadata (not orders themselves) may be out of the SEC 17a-4 retention perimeter. Practical scope-reduction strategies: tokenize at the edge (Stripe, Marqeta, Basis Theory) so internal services only ever see tokens; keep regulated datastores (ledger, order book, card vault) in dedicated VPCs with narrow IAM; route regulated-data logs to a separate SIEM stream so the main observability stack stays out of scope; design SDK boundaries where PII-sensitive fields are never emitted to general-purpose workers.
14
+
15
+ Audit trails are the backbone of every fintech regime. Requirements vary but share a common shape: **who did what, to what, when, from where, and with what authorization** — and the log must be tamper-evident and retained for a regulation-specific minimum (SEC 17a-4: 6 years, first 2 easily accessible; SOX: 7 years; GDPR: duration of legitimate-use plus documented retention; PCI-DSS: 1 year minimum, 3 months immediately available). Append-only database tables (enforced via triggers that reject UPDATE/DELETE) satisfy "append-only" but not "tamper-evident" — a DBA with superuser can still alter the table. Tamper-evidence requires either external WORM storage (AWS S3 Object Lock in compliance mode, AWS QLDB, immutable Kafka with retention holds) or cryptographic chaining (each log entry includes the hash of the previous entry, forming a Merkle-like chain verified on read).
16
+
17
+ Segregation of duties (SoD) is a control, not just good hygiene. For any high-value or irreversible action — wire transfer over a threshold, customer account close, prod DB schema migration, release to production — one human must initiate and a *different* human with appropriate role must approve. SoD failures are the most common SOX/SOC 2 audit findings for fintech engineering orgs. Enforce SoD in the application layer (two-person workflow state machines), in the deploy layer (GitHub required reviewers, protected branches), and in IAM (separate break-glass roles that require another operator to grant). Auditors will ask: "Can the engineer who wrote the code also deploy it to prod? Can the same person initiate and approve a payout?" The answer must be no, with technical enforcement — not policy.
18
+
19
+ Encryption is assumed baseline: TLS 1.2+ in transit (1.3 preferred), AES-256 at rest via KMS-managed keys (AWS KMS, GCP Cloud KMS, Azure Key Vault, HashiCorp Vault Transit). Field-level encryption for the most sensitive data (SSN, PAN, bank account numbers) in addition to disk encryption, with keys rotated annually. See also `backend-fintech-ledger.md` for double-entry ledger design and `backend-fintech-broker-integration.md` for broker-dealer specific records.
20
+
21
+ ## Deep Guidance
22
+
23
+ ### PCI-DSS Scoping and Tokenization
24
+
25
+ PCI-DSS v4.0 applies to any system that stores, processes, or transmits cardholder data (CHD) — the Primary Account Number (PAN), cardholder name, expiration date, service code — or sensitive authentication data (full track, CVV, PIN). The cost of compliance is roughly quadratic in the scope of the "cardholder data environment" (CDE): every service inside the CDE needs quarterly ASV scans, annual penetration tests, hardened configurations, FIM, and quarterly access reviews.
26
+
27
+ **Scope reduction via tokenization** is the dominant pattern. Instead of your application receiving raw PANs, the card is submitted directly from the browser/mobile app to a tokenization provider (Stripe Elements, Braintree Hosted Fields, Marqeta, Basis Theory, Very Good Security) which returns an opaque token. Your backend stores only the token. The card vault is the provider's CDE; your systems are *SAQ A* eligible (the lightest form).
28
+
29
+ If you must hold PANs directly, consider a dedicated card-vault microservice with its own VPC, its own database, its own deploy pipeline, its own on-call, and narrow IAM so the rest of the org — and most of engineering — is outside the CDE. PAN storage requires strong cryptography with documented key management; display must be truncated (first 6, last 4 at most) unless there's a documented business need.
30
+
31
+ Never log PANs, CVVs, or track data. Install log-scrubbing middleware that redacts 13–19 digit sequences matching the Luhn check. Same for error reports — configure Sentry/Datadog scrubbing rules and test them.
32
+
33
+ ### SOC 2 Type I vs Type II, and What Engineering Owns
34
+
35
+ SOC 2 is an attestation framework (not a law) under AICPA TSC (Trust Services Criteria). Type I is a point-in-time assessment of control *design*. Type II assesses *operating effectiveness* over a period (typically 6–12 months). Enterprise customers routinely require Type II before signing.
36
+
37
+ Trust Services Criteria: **Security (CC1–CC9)** is mandatory; Availability, Confidentiality, Processing Integrity, and Privacy are optional but common. Engineering-touchable criteria:
38
+
39
+ - **CC6.1–CC6.8 (Logical Access):** IAM, MFA, offboarding, periodic access reviews. Auditors want evidence of MFA enforced, ex-employee access revoked within N days, quarterly access reviews recorded.
40
+ - **CC7.1–CC7.5 (System Operations):** Monitoring, incident response, change detection. Expect to produce alerting config, incident postmortems, and evidence that anomalies were detected and triaged.
41
+ - **CC8.1 (Change Management):** Every production change must be authorized, tested, and reviewed. Auditors will sample production deploys and ask for the associated PR, reviewer approval, test results, and rollback plan.
42
+ - **A1.1–A1.3 (Availability):** Uptime targets, DR tests, backup verification. Run documented DR drills annually.
43
+
44
+ Automate evidence collection. Tools like Drata, Vanta, Secureframe, Sprinto pull evidence directly from GitHub (PR approvals), AWS (IAM state), Datadog (monitors), HRIS (offboarding). Manual evidence collection at audit time is the most common cause of audit delays.
45
+
46
+ ### SEC Rule 17a-4 and FINRA Supervisory Records
47
+
48
+ Broker-dealers in the US must retain certain records under SEC Rule 17a-4 (and related 17a-3). Key engineering implications:
49
+
50
+ - **Retention periods:** Most records — 6 years, with the first 2 years "easily accessible." Customer account records — lifetime of the account plus 6 years. Trade blotters, order tickets, and communications have specific classifications.
51
+ - **Format:** Historically WORM (Write-Once-Read-Many) with audit-system-of-records. The 2022 amendment to 17a-4(f) now permits an "electronic record-keeping system" that uses an audit trail to track and verify changes, as an alternative to strict WORM. Either way: the record must be non-rewriteable, non-erasable, and verifiable.
52
+ - **Third-party access:** You must designate a third party with the ability to download records if the firm becomes unavailable (D3P letter).
53
+ - **Indexing and retrieval:** Records must be indexed and retrievable within a reasonable time for regulatory request.
54
+
55
+ FINRA Rule 3110 imposes supervisory obligations: written supervisory procedures (WSPs), review of correspondence, review of trade exceptions. Engineering typically supports these via queryable audit logs and exception-reporting pipelines.
56
+
57
+ Common implementation: trade-event stream written to AWS S3 with Object Lock in **compliance mode** (not governance — compliance mode cannot be disabled even by root) with a 6-year retention period, fed by an immutable Kafka topic, with a parallel queryable index (OpenSearch, Postgres read model) for retrieval. See also `backend-fintech-ledger.md` and `backend-fintech-order-lifecycle.md`.
58
+
59
+ ### Immutable Audit Log Patterns
60
+
61
+ **Pattern 1: Append-only table with trigger-enforced immutability (Postgres).**
62
+
63
+ ```sql
64
+ CREATE TABLE audit_log (
65
+ id BIGSERIAL PRIMARY KEY,
66
+ occurred_at TIMESTAMPTZ NOT NULL DEFAULT now(),
67
+ actor_id UUID NOT NULL,
68
+ actor_type TEXT NOT NULL, -- 'user' | 'system' | 'api_key'
69
+ action TEXT NOT NULL, -- 'order.submit' | 'account.close'
70
+ resource_type TEXT NOT NULL,
71
+ resource_id TEXT NOT NULL,
72
+ request_id UUID NOT NULL,
73
+ ip_address INET,
74
+ user_agent TEXT,
75
+ payload JSONB NOT NULL,
76
+ prev_hash BYTEA NOT NULL, -- hash of previous row's row_hash
77
+ row_hash BYTEA NOT NULL -- hash of this row's content + prev_hash
78
+ );
79
+
80
+ CREATE OR REPLACE FUNCTION audit_log_immutable()
81
+ RETURNS trigger LANGUAGE plpgsql AS $$
82
+ BEGIN
83
+ RAISE EXCEPTION 'audit_log is append-only';
84
+ END;
85
+ $$;
86
+
87
+ CREATE TRIGGER audit_log_no_update BEFORE UPDATE ON audit_log
88
+ FOR EACH ROW EXECUTE FUNCTION audit_log_immutable();
89
+ CREATE TRIGGER audit_log_no_delete BEFORE DELETE ON audit_log
90
+ FOR EACH ROW EXECUTE FUNCTION audit_log_immutable();
91
+
92
+ REVOKE UPDATE, DELETE, TRUNCATE ON audit_log FROM PUBLIC;
93
+ ```
94
+
95
+ Triggers prevent UPDATE/DELETE via the application path. A DBA with BYPASSRLS or superuser can still mutate — which is why tamper-evidence needs cryptographic chaining or external WORM.
96
+
97
+ **Pattern 2: Hash-chain computation for tamper evidence.**
98
+
99
+ ```typescript
100
+ import { createHash } from 'node:crypto';
101
+
102
+ function rowHash(prevHash: Buffer, row: AuditRow): Buffer {
103
+ const canonical = JSON.stringify({
104
+ occurredAt: row.occurredAt.toISOString(),
105
+ actorId: row.actorId,
106
+ action: row.action,
107
+ resourceType: row.resourceType,
108
+ resourceId: row.resourceId,
109
+ payload: row.payload, // must be canonicalized (sorted keys)
110
+ });
111
+ return createHash('sha256').update(prevHash).update(canonical).digest();
112
+ }
113
+
114
+ // Verification: walk the chain, recompute each hash, compare to stored row_hash.
115
+ // Any mutation anywhere in the chain invalidates all subsequent hashes.
116
+ // Publish the latest root hash daily (to an external WORM store, or public
117
+ // notary like OpenTimestamps) so even a full-DB rewrite is detectable.
118
+ ```
119
+
120
+ **Pattern 3: External WORM.** AWS QLDB provides a managed ledger with cryptographic verification built in (but note AWS announced QLDB end-of-support in 2025; current guidance is Aurora PostgreSQL with application-layer chaining). Immutable Kafka (with compaction disabled and retention set to forever) plus S3 Object Lock in compliance mode is the more durable path today.
121
+
122
+ ### Tokenization and Scope-Reduction Boundary
123
+
124
+ ```text
125
+ // Scope-reduction sequence — card capture via hosted fields
126
+ //
127
+ // Browser Payment Provider Your Backend
128
+ // | (CDE) (out of CDE)
129
+ // |----- PAN + CVV ------->| |
130
+ // | |-- vault + tokenize ------>|
131
+ // | | |
132
+ // |<-- token (tok_abc) ----| |
133
+ // |------------ token ---------------------------------|
134
+ // | |
135
+ // | [store token, last4, brand]
136
+ // | [never sees raw PAN]
137
+ ```
138
+
139
+ The boundary is enforced by: (a) hosted fields JavaScript that POSTs directly to the provider, (b) CSP and network rules that prevent your backend from reaching card-network endpoints directly, (c) log scrubbing, (d) an SAQ A attestation that documents you never touch CHD.
140
+
141
+ ### Encryption-at-Rest and In-Transit Expectations
142
+
143
+ - **In transit:** TLS 1.2 minimum (1.3 preferred). Internal service-to-service: mutual TLS or a service mesh (Istio, Linkerd, Consul Connect) that provides mTLS. No plaintext — even on private networks — for regulated data.
144
+ - **At rest:** Disk encryption (EBS, RDS, S3 SSE) is the baseline but not sufficient for the most sensitive data. Add field-level encryption for SSN, PAN, bank account numbers, MFA seeds. Use envelope encryption: data encryption keys (DEKs) wrap each record, KMS holds the key-encryption key (KEK). AWS KMS, GCP Cloud KMS, Azure Key Vault, or HashiCorp Vault Transit all support this pattern.
145
+ - **Key rotation:** Annual minimum for KEKs. DEKs rotate via re-encryption on access patterns. Document the rotation runbook — auditors will ask.
146
+ - **Backups:** Encrypted with the same rigor as primary data. Test restore quarterly, document the result.
147
+
148
+ ### Data Residency and Localization
149
+
150
+ EU GDPR, UK GDPR, India DPDP, Brazil LGPD, and increasingly US state laws (California CCPA/CPRA, Colorado CPA) impose data-locality and cross-border-transfer constraints. Design choices:
151
+
152
+ - **Region-per-tenant:** A tenant lives in exactly one region; all their data (primary DB, backups, caches, search indexes, logs) stays in that region. Route requests by tenant → region mapping at the edge (Cloudflare Workers, AWS Global Accelerator, route-53 geolocation).
153
+ - **Partition keys include region:** So you can evacuate a region by range-scanning the partition map.
154
+ - **No global secondary indexes that span regions.** That includes search indexes, analytics warehouses, and audit aggregators.
155
+ - **Document your data-flow map.** GDPR Article 30 records of processing activities require it. Keep it in-repo and generated from code where possible (schema annotations → flow map).
156
+
157
+ Retrofitting region isolation after launch is 10x the cost of building it in. If there's any chance you'll serve EU customers, design for it on day one.
158
+
159
+ ### Change-Management Evidence (SOC 2 CC8.1)
160
+
161
+ Your CI/CD pipeline is auditor-facing. For every production deploy, auditors want to see:
162
+
163
+ - **Artifact provenance:** The exact commit SHA, the container digest (sha256), the builder identity. SLSA Level 2+ provenance (via GitHub Actions OIDC, Sigstore cosign) makes this cryptographically verifiable.
164
+ - **Approval trail:** PR with required reviewers, required status checks (tests, security scans), branch protection enforced. No direct pushes to main. GitHub's audit log captures this.
165
+ - **Test evidence:** Test results archived per deploy (JUnit XML, coverage report) and tied to the deploy via the commit SHA.
166
+ - **Rollback capability:** Documented procedure, tested in production at least annually. Include automated rollback on health-check failure.
167
+ - **Deploy record:** Who triggered, what artifact, when, to what environment, what configuration changes. Deploy tools (ArgoCD, Spinnaker, GitHub Deployments) produce this natively.
168
+
169
+ Separate the CI identity from the CD identity. The CI identity can build and sign; only the CD identity (with stricter controls) can deploy to prod. This is SoD at the pipeline layer.
170
+
171
+ ### Known Pitfalls
172
+
173
+ - **Secrets in logs.** A developer logs `console.log(req.headers)` including `Authorization`. Prevention: structured-logging middleware with an allowlist of fields, deny-list of headers (Authorization, Cookie, X-API-Key), unit tests asserting redaction.
174
+ - **Debug-mode leaks.** Stack traces, `DEBUG=*`, SQL echo — off in production. Verify via a smoke test in the deploy pipeline.
175
+ - **PII in error reports.** Sentry/Datadog by default captures request bodies. Configure beforeSend/scrubbing for known PII field names; validate with synthetic PII in staging.
176
+ - **Background workers bypassing request-context audit hooks.** A cron job that updates account balances without writing to `audit_log` creates a compliance gap. Audit-log writes must live in the domain layer (on the entity mutation), not in HTTP middleware.
177
+ - **Shadow datastores.** Read replicas, BI warehouses, and ML feature stores often get regulated data without inheriting the controls of the primary store. Every copy of the data must inherit the same retention, access, and encryption rules.
178
+ - **Third-party SaaS with unaudited access.** Customer-support tools (Intercom, Zendesk), analytics (Segment, Amplitude), and AI assistants can exfiltrate PII. Gate every new SaaS through a vendor-review process with a DPA.
179
+ - **Employee access without audit.** Support staff viewing customer accounts must log every view (access-log table, reason code). This is a common SOC 2 finding and GLBA Safeguards Rule expectation.
180
+
181
+ See also `backend-fintech-ledger.md`, `backend-fintech-testing.md`, and `backend-fintech-observability.md`.