@alneas/eaos-server 0.0.0-stage → 0.2.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 (360) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/LICENSE +21 -0
  3. package/README.ja.md +49 -0
  4. package/README.md +46 -2
  5. package/dist/agentic/agent-jobs.d.ts +333 -0
  6. package/dist/agentic/agent-jobs.js +15 -0
  7. package/dist/agentic/agent-jobs.js.map +1 -0
  8. package/dist/agentic/agent-node.d.ts +40 -0
  9. package/dist/agentic/agent-node.js +7 -0
  10. package/dist/agentic/agent-node.js.map +1 -0
  11. package/dist/agentic/agent-runner.d.ts +33 -0
  12. package/dist/agentic/agent-runner.js +7 -0
  13. package/dist/agentic/agent-runner.js.map +1 -0
  14. package/dist/agentic/ai-broker.d.ts +169 -0
  15. package/dist/agentic/ai-broker.js +8 -0
  16. package/dist/agentic/ai-broker.js.map +1 -0
  17. package/dist/agentic/atomic-checkpointer.d.ts +137 -0
  18. package/dist/agentic/atomic-checkpointer.js +9 -0
  19. package/dist/agentic/atomic-checkpointer.js.map +1 -0
  20. package/dist/agentic/autonomous-worker.d.ts +122 -0
  21. package/dist/agentic/autonomous-worker.js +10 -0
  22. package/dist/agentic/autonomous-worker.js.map +1 -0
  23. package/dist/agentic/event-bridge.d.ts +350 -0
  24. package/dist/agentic/event-bridge.js +9 -0
  25. package/dist/agentic/event-bridge.js.map +1 -0
  26. package/dist/agentic/executor.d.ts +91 -0
  27. package/dist/agentic/executor.js +12 -0
  28. package/dist/agentic/executor.js.map +1 -0
  29. package/dist/agentic/graph-projection.d.ts +119 -0
  30. package/dist/agentic/graph-projection.js +7 -0
  31. package/dist/agentic/graph-projection.js.map +1 -0
  32. package/dist/agentic/graph.d.ts +524 -0
  33. package/dist/agentic/graph.js +7 -0
  34. package/dist/agentic/graph.js.map +1 -0
  35. package/dist/agentic/index.d.ts +28 -0
  36. package/dist/agentic/index.js +87 -0
  37. package/dist/agentic/index.js.map +1 -0
  38. package/dist/agentic/local-runner.d.ts +106 -0
  39. package/dist/agentic/local-runner.js +7 -0
  40. package/dist/agentic/local-runner.js.map +1 -0
  41. package/dist/agentic/remote-executor.d.ts +67 -0
  42. package/dist/agentic/remote-executor.js +8 -0
  43. package/dist/agentic/remote-executor.js.map +1 -0
  44. package/dist/agentic/types.d.ts +157 -0
  45. package/dist/agentic/types.js +7 -0
  46. package/dist/agentic/types.js.map +1 -0
  47. package/dist/agentic/workflow-catalog.d.ts +152 -0
  48. package/dist/agentic/workflow-catalog.js +7 -0
  49. package/dist/agentic/workflow-catalog.js.map +1 -0
  50. package/dist/agentic/workflow-compiler.d.ts +44 -0
  51. package/dist/agentic/workflow-compiler.js +8 -0
  52. package/dist/agentic/workflow-compiler.js.map +1 -0
  53. package/dist/agentic/workflow-coordinator.d.ts +170 -0
  54. package/dist/agentic/workflow-coordinator.js +8 -0
  55. package/dist/agentic/workflow-coordinator.js.map +1 -0
  56. package/dist/agentic/workflow-manager.d.ts +91 -0
  57. package/dist/agentic/workflow-manager.js +7 -0
  58. package/dist/agentic/workflow-manager.js.map +1 -0
  59. package/dist/agentic/workflow-registry.d.ts +334 -0
  60. package/dist/agentic/workflow-registry.js +7 -0
  61. package/dist/agentic/workflow-registry.js.map +1 -0
  62. package/dist/artifacts-CteHmCMI.d.ts +614 -0
  63. package/dist/auth-hooks/api-key.d.ts +75 -0
  64. package/dist/auth-hooks/api-key.js +7 -0
  65. package/dist/auth-hooks/api-key.js.map +1 -0
  66. package/dist/auth-hooks/index.d.ts +80 -0
  67. package/dist/auth-hooks/index.js +28 -0
  68. package/dist/auth-hooks/index.js.map +1 -0
  69. package/dist/browser-auth/account-memberships.d.ts +213 -0
  70. package/dist/browser-auth/account-memberships.js +13 -0
  71. package/dist/browser-auth/account-memberships.js.map +1 -0
  72. package/dist/browser-auth/index.d.ts +8 -0
  73. package/dist/browser-auth/index.js +59 -0
  74. package/dist/browser-auth/index.js.map +1 -0
  75. package/dist/browser-auth/kratos-session.d.ts +37 -0
  76. package/dist/browser-auth/kratos-session.js +7 -0
  77. package/dist/browser-auth/kratos-session.js.map +1 -0
  78. package/dist/browser-auth/platform-session.d.ts +120 -0
  79. package/dist/browser-auth/platform-session.js +7 -0
  80. package/dist/browser-auth/platform-session.js.map +1 -0
  81. package/dist/browser-auth/public-memberships.d.ts +22 -0
  82. package/dist/browser-auth/public-memberships.js +7 -0
  83. package/dist/browser-auth/public-memberships.js.map +1 -0
  84. package/dist/cache/index.d.ts +181 -0
  85. package/dist/cache/index.js +197 -0
  86. package/dist/cache/index.js.map +1 -0
  87. package/dist/chunk-32746UQZ.js +131 -0
  88. package/dist/chunk-32746UQZ.js.map +1 -0
  89. package/dist/chunk-3NLSWP2Y.js +223 -0
  90. package/dist/chunk-3NLSWP2Y.js.map +1 -0
  91. package/dist/chunk-3P4XTTQH.js +346 -0
  92. package/dist/chunk-3P4XTTQH.js.map +1 -0
  93. package/dist/chunk-3VITSHN3.js +15 -0
  94. package/dist/chunk-3VITSHN3.js.map +1 -0
  95. package/dist/chunk-46UWPEW2.js +226 -0
  96. package/dist/chunk-46UWPEW2.js.map +1 -0
  97. package/dist/chunk-4BARRLJR.js +180 -0
  98. package/dist/chunk-4BARRLJR.js.map +1 -0
  99. package/dist/chunk-4VORUFKR.js +45 -0
  100. package/dist/chunk-4VORUFKR.js.map +1 -0
  101. package/dist/chunk-55D4ERWO.js +1 -0
  102. package/dist/chunk-55D4ERWO.js.map +1 -0
  103. package/dist/chunk-5TBBZIND.js +18 -0
  104. package/dist/chunk-5TBBZIND.js.map +1 -0
  105. package/dist/chunk-5WOZWPZA.js +211 -0
  106. package/dist/chunk-5WOZWPZA.js.map +1 -0
  107. package/dist/chunk-6CBVZ6CT.js +168 -0
  108. package/dist/chunk-6CBVZ6CT.js.map +1 -0
  109. package/dist/chunk-6XZXYNJR.js +74 -0
  110. package/dist/chunk-6XZXYNJR.js.map +1 -0
  111. package/dist/chunk-753HWDSC.js +222 -0
  112. package/dist/chunk-753HWDSC.js.map +1 -0
  113. package/dist/chunk-7AHSS7SO.js +81 -0
  114. package/dist/chunk-7AHSS7SO.js.map +1 -0
  115. package/dist/chunk-7FTJN4SA.js +1 -0
  116. package/dist/chunk-7FTJN4SA.js.map +1 -0
  117. package/dist/chunk-AVDMLTPF.js +24 -0
  118. package/dist/chunk-AVDMLTPF.js.map +1 -0
  119. package/dist/chunk-BKAHUEIT.js +95 -0
  120. package/dist/chunk-BKAHUEIT.js.map +1 -0
  121. package/dist/chunk-CJ7OFEF7.js +134 -0
  122. package/dist/chunk-CJ7OFEF7.js.map +1 -0
  123. package/dist/chunk-EJKWDZIU.js +111 -0
  124. package/dist/chunk-EJKWDZIU.js.map +1 -0
  125. package/dist/chunk-F2J6IQ6I.js +247 -0
  126. package/dist/chunk-F2J6IQ6I.js.map +1 -0
  127. package/dist/chunk-F3O7CELI.js +23 -0
  128. package/dist/chunk-F3O7CELI.js.map +1 -0
  129. package/dist/chunk-F4IITPGX.js +221 -0
  130. package/dist/chunk-F4IITPGX.js.map +1 -0
  131. package/dist/chunk-FD6VC2NA.js +66 -0
  132. package/dist/chunk-FD6VC2NA.js.map +1 -0
  133. package/dist/chunk-FRRRWAHO.js +14 -0
  134. package/dist/chunk-FRRRWAHO.js.map +1 -0
  135. package/dist/chunk-HCKAGLLB.js +17 -0
  136. package/dist/chunk-HCKAGLLB.js.map +1 -0
  137. package/dist/chunk-HVC7FG6B.js +191 -0
  138. package/dist/chunk-HVC7FG6B.js.map +1 -0
  139. package/dist/chunk-HXFVOR7Z.js +496 -0
  140. package/dist/chunk-HXFVOR7Z.js.map +1 -0
  141. package/dist/chunk-IJWYS2PF.js +122 -0
  142. package/dist/chunk-IJWYS2PF.js.map +1 -0
  143. package/dist/chunk-INCHXAFK.js +1395 -0
  144. package/dist/chunk-INCHXAFK.js.map +1 -0
  145. package/dist/chunk-JT6BRGQM.js +1 -0
  146. package/dist/chunk-JT6BRGQM.js.map +1 -0
  147. package/dist/chunk-L6PMAEMB.js +115 -0
  148. package/dist/chunk-L6PMAEMB.js.map +1 -0
  149. package/dist/chunk-LOUGW6MU.js +126 -0
  150. package/dist/chunk-LOUGW6MU.js.map +1 -0
  151. package/dist/chunk-LYPZPE53.js +129 -0
  152. package/dist/chunk-LYPZPE53.js.map +1 -0
  153. package/dist/chunk-NNCCN7JP.js +186 -0
  154. package/dist/chunk-NNCCN7JP.js.map +1 -0
  155. package/dist/chunk-PBC3MZUL.js +212 -0
  156. package/dist/chunk-PBC3MZUL.js.map +1 -0
  157. package/dist/chunk-R4ADP2DC.js +20 -0
  158. package/dist/chunk-R4ADP2DC.js.map +1 -0
  159. package/dist/chunk-RHN4Y6VS.js +44 -0
  160. package/dist/chunk-RHN4Y6VS.js.map +1 -0
  161. package/dist/chunk-SDJNYPYK.js +770 -0
  162. package/dist/chunk-SDJNYPYK.js.map +1 -0
  163. package/dist/chunk-UGNTCOVR.js +28 -0
  164. package/dist/chunk-UGNTCOVR.js.map +1 -0
  165. package/dist/chunk-VC2PCK2S.js +31 -0
  166. package/dist/chunk-VC2PCK2S.js.map +1 -0
  167. package/dist/chunk-VF5YDZJC.js +305 -0
  168. package/dist/chunk-VF5YDZJC.js.map +1 -0
  169. package/dist/chunk-VISRDVTK.js +675 -0
  170. package/dist/chunk-VISRDVTK.js.map +1 -0
  171. package/dist/chunk-W6O3LVBY.js +102 -0
  172. package/dist/chunk-W6O3LVBY.js.map +1 -0
  173. package/dist/chunk-WFEAKJLR.js +47 -0
  174. package/dist/chunk-WFEAKJLR.js.map +1 -0
  175. package/dist/chunk-XIEY2OF4.js +67 -0
  176. package/dist/chunk-XIEY2OF4.js.map +1 -0
  177. package/dist/chunk-XITLHDG3.js +37 -0
  178. package/dist/chunk-XITLHDG3.js.map +1 -0
  179. package/dist/chunk-XMWPYZWZ.js +61 -0
  180. package/dist/chunk-XMWPYZWZ.js.map +1 -0
  181. package/dist/chunk-XUP6CNOM.js +34 -0
  182. package/dist/chunk-XUP6CNOM.js.map +1 -0
  183. package/dist/chunk-YABTQZDJ.js +25 -0
  184. package/dist/chunk-YABTQZDJ.js.map +1 -0
  185. package/dist/chunk-YMHVRBUU.js +199 -0
  186. package/dist/chunk-YMHVRBUU.js.map +1 -0
  187. package/dist/chunk-YMJ7BRKU.js +510 -0
  188. package/dist/chunk-YMJ7BRKU.js.map +1 -0
  189. package/dist/chunk-ZC3XWU7B.js +28 -0
  190. package/dist/chunk-ZC3XWU7B.js.map +1 -0
  191. package/dist/chunk-ZHMTD5KV.js +90 -0
  192. package/dist/chunk-ZHMTD5KV.js.map +1 -0
  193. package/dist/chunk-ZWZDLOE7.js +160 -0
  194. package/dist/chunk-ZWZDLOE7.js.map +1 -0
  195. package/dist/db-package/db/local-first-sqlite-adapter.d.ts +432 -0
  196. package/dist/db-package/db/local-first-sqlite-adapter.js +7 -0
  197. package/dist/db-package/db/local-first-sqlite-adapter.js.map +1 -0
  198. package/dist/db-package/index.d.ts +7 -0
  199. package/dist/db-package/index.js +16 -0
  200. package/dist/db-package/index.js.map +1 -0
  201. package/dist/db-package/migration/aggregator.d.ts +86 -0
  202. package/dist/db-package/migration/aggregator.js +7 -0
  203. package/dist/db-package/migration/aggregator.js.map +1 -0
  204. package/dist/db-package/migration/index.d.ts +3 -0
  205. package/dist/db-package/migration/index.js +8 -0
  206. package/dist/db-package/migration/index.js.map +1 -0
  207. package/dist/engine.d.ts +205 -0
  208. package/dist/engine.js +20 -0
  209. package/dist/engine.js.map +1 -0
  210. package/dist/entitlements/index.d.ts +313 -0
  211. package/dist/entitlements/index.js +23 -0
  212. package/dist/entitlements/index.js.map +1 -0
  213. package/dist/execution/chat.d.ts +7 -0
  214. package/dist/execution/chat.js +7 -0
  215. package/dist/execution/chat.js.map +1 -0
  216. package/dist/execution/index.d.ts +342 -0
  217. package/dist/execution/index.js +66 -0
  218. package/dist/execution/index.js.map +1 -0
  219. package/dist/execution/tool-agent.d.ts +7 -0
  220. package/dist/execution/tool-agent.js +8 -0
  221. package/dist/execution/tool-agent.js.map +1 -0
  222. package/dist/extensions/index.d.ts +172 -0
  223. package/dist/extensions/index.js +95 -0
  224. package/dist/extensions/index.js.map +1 -0
  225. package/dist/health/index.d.ts +35 -0
  226. package/dist/health/index.js +7 -0
  227. package/dist/health/index.js.map +1 -0
  228. package/dist/index.d.ts +43 -0
  229. package/dist/index.js +161 -0
  230. package/dist/index.js.map +1 -0
  231. package/dist/lifecycle/index.d.ts +133 -0
  232. package/dist/lifecycle/index.js +13 -0
  233. package/dist/lifecycle/index.js.map +1 -0
  234. package/dist/lifecycle-engine/backend-migration.d.ts +2 -0
  235. package/dist/lifecycle-engine/backend-migration.js +11 -0
  236. package/dist/lifecycle-engine/backend-migration.js.map +1 -0
  237. package/dist/lifecycle-engine/baseline.d.ts +2 -0
  238. package/dist/lifecycle-engine/baseline.js +9 -0
  239. package/dist/lifecycle-engine/baseline.js.map +1 -0
  240. package/dist/lifecycle-engine/index.d.ts +1497 -0
  241. package/dist/lifecycle-engine/index.js +41 -0
  242. package/dist/lifecycle-engine/index.js.map +1 -0
  243. package/dist/lifecycle-engine/platform/artifacts.d.ts +4 -0
  244. package/dist/lifecycle-engine/platform/artifacts.js +7 -0
  245. package/dist/lifecycle-engine/platform/artifacts.js.map +1 -0
  246. package/dist/lifecycle-engine/platform/control-plane.d.ts +12 -0
  247. package/dist/lifecycle-engine/platform/control-plane.js +2 -0
  248. package/dist/lifecycle-engine/platform/control-plane.js.map +1 -0
  249. package/dist/lifecycle-engine/platform/database-router.d.ts +118 -0
  250. package/dist/lifecycle-engine/platform/database-router.js +7 -0
  251. package/dist/lifecycle-engine/platform/database-router.js.map +1 -0
  252. package/dist/lifecycle-engine/platform/deployment-placements.d.ts +464 -0
  253. package/dist/lifecycle-engine/platform/deployment-placements.js +16 -0
  254. package/dist/lifecycle-engine/platform/deployment-placements.js.map +1 -0
  255. package/dist/lifecycle-engine/platform/deployment.d.ts +4 -0
  256. package/dist/lifecycle-engine/platform/deployment.js +17 -0
  257. package/dist/lifecycle-engine/platform/deployment.js.map +1 -0
  258. package/dist/lifecycle-engine/platform/index.d.ts +737 -0
  259. package/dist/lifecycle-engine/platform/index.js +60 -0
  260. package/dist/lifecycle-engine/platform/index.js.map +1 -0
  261. package/dist/lifecycle-engine/platform/runtime.d.ts +80 -0
  262. package/dist/lifecycle-engine/platform/runtime.js +11 -0
  263. package/dist/lifecycle-engine/platform/runtime.js.map +1 -0
  264. package/dist/lifecycle-engine/platform/site-publication.d.ts +102 -0
  265. package/dist/lifecycle-engine/platform/site-publication.js +7 -0
  266. package/dist/lifecycle-engine/platform/site-publication.js.map +1 -0
  267. package/dist/lifecycle-engine/platform/user-directory.d.ts +139 -0
  268. package/dist/lifecycle-engine/platform/user-directory.js +13 -0
  269. package/dist/lifecycle-engine/platform/user-directory.js.map +1 -0
  270. package/dist/mcp.d.ts +92 -0
  271. package/dist/mcp.js +10 -0
  272. package/dist/mcp.js.map +1 -0
  273. package/dist/middleware/auth.d.ts +118 -0
  274. package/dist/middleware/auth.js +7 -0
  275. package/dist/middleware/auth.js.map +1 -0
  276. package/dist/middleware/error-handler.d.ts +35 -0
  277. package/dist/middleware/error-handler.js +7 -0
  278. package/dist/middleware/error-handler.js.map +1 -0
  279. package/dist/middleware/module-guard.d.ts +33 -0
  280. package/dist/middleware/module-guard.js +7 -0
  281. package/dist/middleware/module-guard.js.map +1 -0
  282. package/dist/platform-admin/index.d.ts +37 -0
  283. package/dist/platform-admin/index.js +11 -0
  284. package/dist/platform-admin/index.js.map +1 -0
  285. package/dist/platform-admin/store.d.ts +346 -0
  286. package/dist/platform-admin/store.js +7 -0
  287. package/dist/platform-admin/store.js.map +1 -0
  288. package/dist/plugin-data/annotations.d.ts +6 -0
  289. package/dist/plugin-data/annotations.js +12 -0
  290. package/dist/plugin-data/annotations.js.map +1 -0
  291. package/dist/plugin-data/atomic-context.d.ts +41 -0
  292. package/dist/plugin-data/atomic-context.js +9 -0
  293. package/dist/plugin-data/atomic-context.js.map +1 -0
  294. package/dist/plugin-data/atomic.d.ts +6 -0
  295. package/dist/plugin-data/atomic.js +26 -0
  296. package/dist/plugin-data/atomic.js.map +1 -0
  297. package/dist/plugin-data/audit.d.ts +6 -0
  298. package/dist/plugin-data/audit.js +12 -0
  299. package/dist/plugin-data/audit.js.map +1 -0
  300. package/dist/plugin-data/commands.d.ts +6 -0
  301. package/dist/plugin-data/commands.js +8 -0
  302. package/dist/plugin-data/commands.js.map +1 -0
  303. package/dist/plugin-data/composite.d.ts +6 -0
  304. package/dist/plugin-data/composite.js +8 -0
  305. package/dist/plugin-data/composite.js.map +1 -0
  306. package/dist/plugin-data/delivery.d.ts +6 -0
  307. package/dist/plugin-data/delivery.js +16 -0
  308. package/dist/plugin-data/delivery.js.map +1 -0
  309. package/dist/plugin-data/index.d.ts +2798 -0
  310. package/dist/plugin-data/index.js +102 -0
  311. package/dist/plugin-data/index.js.map +1 -0
  312. package/dist/plugin-data/json-documents.d.ts +6 -0
  313. package/dist/plugin-data/json-documents.js +16 -0
  314. package/dist/plugin-data/json-documents.js.map +1 -0
  315. package/dist/plugin-data/outbox.d.ts +6 -0
  316. package/dist/plugin-data/outbox.js +16 -0
  317. package/dist/plugin-data/outbox.js.map +1 -0
  318. package/dist/plugin-data/scoped-records.d.ts +6 -0
  319. package/dist/plugin-data/scoped-records.js +10 -0
  320. package/dist/plugin-data/scoped-records.js.map +1 -0
  321. package/dist/plugin-sdk.d.ts +41 -0
  322. package/dist/plugin-sdk.js +10 -0
  323. package/dist/plugin-sdk.js.map +1 -0
  324. package/dist/sql-lifecycle/index.d.ts +138 -0
  325. package/dist/sql-lifecycle/index.js +167 -0
  326. package/dist/sql-lifecycle/index.js.map +1 -0
  327. package/dist/storage/index.d.ts +2 -0
  328. package/dist/storage/index.js +13 -0
  329. package/dist/storage/index.js.map +1 -0
  330. package/dist/storage/s3-compatible.d.ts +139 -0
  331. package/dist/storage/s3-compatible.js +11 -0
  332. package/dist/storage/s3-compatible.js.map +1 -0
  333. package/dist/tenant.d.ts +108 -0
  334. package/dist/tenant.js +9 -0
  335. package/dist/tenant.js.map +1 -0
  336. package/dist/workspace-context.d.ts +119 -0
  337. package/dist/workspace-context.js +9 -0
  338. package/dist/workspace-context.js.map +1 -0
  339. package/docs/api.md +30560 -0
  340. package/docs/db/README.md +49 -0
  341. package/docs/db/docs/api.md +1613 -0
  342. package/docs/db/docs/guide.md +47 -0
  343. package/docs/entitlements.md +60 -0
  344. package/docs/guide.md +92 -0
  345. package/docs/index.md +57 -0
  346. package/docs/ja/db/README.md +46 -0
  347. package/docs/ja/db/docs/guide.md +53 -0
  348. package/docs/ja/guide.md +46 -0
  349. package/docs/ja/lifecycle/README.md +46 -0
  350. package/docs/ja/lifecycle/docs/guide.md +45 -0
  351. package/docs/ja/storage/README.md +46 -0
  352. package/docs/ja/storage/docs/guide.md +43 -0
  353. package/docs/lifecycle/README.md +49 -0
  354. package/docs/lifecycle/docs/api.md +3770 -0
  355. package/docs/lifecycle/docs/baseline.md +50 -0
  356. package/docs/lifecycle/docs/guide.md +47 -0
  357. package/docs/storage/README.md +49 -0
  358. package/docs/storage/docs/api.md +470 -0
  359. package/docs/storage/docs/guide.md +47 -0
  360. package/package.json +354 -4
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/db-package/db/local-first-sqlite-adapter.ts"],"sourcesContent":["/**\n * @fileoverview Local-First SQLite DataAccessAdapter with Adaptive Sync & WAL Lock Prevention\n *\n * Implements @alneas/appkit-core DataAccessAdapter interface.\n * - Reads/writes locally to SQLite before attempting remote delivery.\n * - Configures SQLite WAL mode, NORMAL synchronous, and busy_timeout to prevent DB locking.\n * - Records mutations in an outbox queue (_outbox_queue) with pre-change snapshots for attempted rollback after permanent delivery failure.\n * - Tracks user access frequency (_access_frequency) to adaptively sync frequently used collections.\n * - Pulls remote incremental changes from an ORA_ROWSCN-capable upstream within batch transactions.\n * - Handles window focus & network online lifecycle hooks for seamless just-in-time synchronization.\n *\n * @module db/local-first-sqlite-adapter\n */\n\nimport type {\n DataAccessAdapter,\n Filter,\n QueryOptions,\n} from '@alneas/appkit-core/data/types';\n\n/**\n * Host SQLite operations required by {@link LocalFirstSqliteAdapter}; execution and transaction ownership belong to the host.\n */\nexport interface SqliteDriver {\n /**\n * Executes SQLite SQL with positional bindings.\n * @param sql - SQL statement, including transaction control statements.\n * @param params - Positional values for question-mark placeholders; omitted for parameterless SQL.\n * @returns Driver-specific execution result, which the adapter does not inspect.\n * @throws Rejects on driver/database failure; callers determine rollback handling.\n \n * @example\n * ```typescript\n * import type { SqliteDriver } from \"@alneas/eaos-server/db\";\n * export async function inspectConnectedSqlite(driver: SqliteDriver) {\n * // The host opens and closes the connection; this statement changes no records.\n * return await driver.execute(\"SELECT 1 AS ready\", []);\n * }\n * ```\n */\n execute(sql: string, params?: any[]): Promise<any>;\n /**\n * Selects SQLite rows using positional bindings.\n * @typeParam T - Row shape expected by the caller; the driver does not establish runtime validation.\n * @param sql - SELECT statement.\n * @param params - Positional values, or omitted for parameterless queries.\n * @returns Matching rows, or an empty array.\n * @throws Rejects on driver/database failure.\n \n * @example\n * ```typescript\n * import type { SqliteDriver } from \"@alneas/eaos-server/db\";\n * export async function selectConnectedSqlite(driver: SqliteDriver) {\n * const rows = await driver.select<{ ready: number }>(\"SELECT 1 AS ready\", []);\n * return rows[0]?.ready; // The type argument does not perform runtime validation.\n * }\n * ```\n */\n select<T = any>(sql: string, params?: any[]): Promise<T[]>;\n}\n\n/**\n * Local outbox lifecycle status; successfully synchronized rows are removed by processOutbox.\n * @see LocalFirstSqliteAdapter.processOutbox.\n */\nexport type SyncStatus = 'pending' | 'syncing' | 'synced' | 'failed';\n\n/**\n * Local mutation and rollback snapshot awaiting upstream synchronization.\n * @see LocalFirstSqliteAdapter.processOutbox.\n */\nexport interface OutboxItem {\n /** Outbox row identifier. */\n id: string;\n /** Target local/upstream table. */\n tableName: string;\n /** Mutation record identifier. */\n recordId: string;\n /** INSERT, UPDATE, or DELETE operation forwarded upstream. */\n operation: 'INSERT' | 'UPDATE' | 'DELETE';\n /** Mutation data; update retains only the supplied changes. */\n payload: Record<string, any>;\n /** Optional pre-mutation row used for rollback; null for inserts or missing snapshots. */\n previousState?: Record<string, any> | null;\n /** Persisted synchronization state. */\n status: SyncStatus;\n /** Optional most recent persisted error description. */\n errorMessage?: string;\n /** Number of recognized transient failures recorded so far. */\n retryCount: number;\n /** ISO creation timestamp used for batch ordering. */\n createdAt: string;\n}\n\n/**\n * Permanent synchronization failure notification emitted after local rollback handling.\n * @see LocalFirstSqliteAdapterOptions.onSyncError.\n */\nexport interface SyncErrorEvent {\n /** Outbox item whose permanent failure caused rollback handling. */\n item: OutboxItem;\n /** Underlying synchronization failure. */\n error: Error;\n /** True when rollback handling reached the callback; it is not a remote rollback guarantee. */\n rolledBack: boolean;\n}\n\n/**\n * Per-table local counters used to rank adaptive synchronization candidates.\n * @see LocalFirstSqliteAdapter.recordAccess.\n */\nexport interface AccessFrequencyRecord {\n /** Tracked collection identifier. */\n table_name: string;\n /** Accumulated read count. */\n read_count: number;\n /** Accumulated write count. */\n write_count: number;\n /** ISO timestamp of the most recently recorded access. */\n last_accessed_at: string;\n}\n\n/**\n * Host drivers, sync behavior, and cleanup-sensitive scheduling for {@link LocalFirstSqliteAdapter}.\n */\nexport interface LocalFirstSqliteAdapterOptions {\n /** SQLite driver instance */\n sqlite: SqliteDriver;\n /** Cloud upstream adapter (e.g. OracleOrdsAdapter / SupabaseAdapter) */\n upstream: DataAccessAdapter;\n /** Optional tenant_id default added only on create when neither tenant_id nor tenantId is supplied; this does not scope reads, updates, deletes, or upstream access. Host authorization remains required. */\n tenantId?: string;\n /** Essential master tables that are always synchronized (Tier 1) */\n essentialTables?: string[];\n /** Callback fired when an outbox sync fails permanently and triggers a rollback */\n onSyncError?: (event: SyncErrorEvent) => void;\n /** Outbox polling interval in milliseconds. Zero disables polling; other falsy values fall back to 5000. @defaultValue 5000 */\n syncIntervalMs?: number;\n /** Whether to immediately trigger background sync after local mutation (default: true) */\n autoSyncOnMutation?: boolean;\n /** Whether to attach window focus / online event listeners (default: true if in browser/tauri) */\n enableLifecycleSync?: boolean;\n}\n\n/**\n * Persists local mutations and an outbox, then synchronizes with a host-provided upstream adapter.\n * @remarks Call initSchema before use; it creates metadata only, so provision domain tables separately. Local mutation and outbox insertion are separate writes, so failure may leave an unqueued local change. No tenant authorization is implemented: tenantId is only a create default. Serialize transaction-sensitive operations on the host driver; processOutbox excludes overlapping calls on this instance only. Call destroy to remove timers/listeners; it does not cancel in-flight work or close either driver.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * return await adapter.find(\"records\");\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\nexport class LocalFirstSqliteAdapter implements DataAccessAdapter {\n /** Optional create-time tenant_id fallback captured from options; not a read or mutation authorization filter. */\n readonly tenantId?: string;\n private sqlite: SqliteDriver;\n private upstream: DataAccessAdapter;\n private onSyncError?: (event: SyncErrorEvent) => void;\n private autoSyncOnMutation: boolean;\n private essentialTables: Set<string>;\n private syncTimer: any = null;\n private isSyncing = false;\n private cleanupListeners?: () => void;\n\n /**\n * Captures drivers and starts optional outbox polling and browser lifecycle listeners.\n * @param options - Host drivers and synchronization settings; see {@link LocalFirstSqliteAdapterOptions}.\n * @remarks The constructor performs no schema initialization. Timers/listeners start immediately unless disabled; call destroy when the instance is no longer needed.\n */\n constructor(options: LocalFirstSqliteAdapterOptions) {\n this.tenantId = options.tenantId;\n this.sqlite = options.sqlite;\n this.upstream = options.upstream;\n this.onSyncError = options.onSyncError;\n this.autoSyncOnMutation = options.autoSyncOnMutation !== false;\n this.essentialTables = new Set(options.essentialTables ?? []);\n\n if (options.syncIntervalMs !== 0) {\n const interval = options.syncIntervalMs || 5000;\n if (typeof setInterval !== 'undefined') {\n this.syncTimer = setInterval(() => this.processOutbox(), interval);\n }\n }\n\n if (options.enableLifecycleSync !== false && typeof window !== 'undefined') {\n this.attachLifecycleListeners();\n }\n }\n\n /**\n * Initializes SQLite synchronization metadata tables and attempts WAL, NORMAL, and 5000 ms busy-timeout pragmas.\n * @returns No value; resolves after metadata table creation. Pragma failures are deliberately ignored.\n * @throws Rejects metadata table creation failures; earlier tables may already exist.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * await adapter.initSchema();\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async initSchema(): Promise<void> {\n // 1. Anti-locking & Concurrency Optimization\n try {\n await this.sqlite.execute(`PRAGMA journal_mode = WAL;`);\n await this.sqlite.execute(`PRAGMA synchronous = NORMAL;`);\n await this.sqlite.execute(`PRAGMA busy_timeout = 5000;`);\n } catch {\n // Ignored if in-memory mock or non-file driver\n }\n\n // 2. Metadata Tables\n await this.sqlite.execute(`\n CREATE TABLE IF NOT EXISTS _sync_meta (\n table_name TEXT PRIMARY KEY,\n last_sync_scn INTEGER NOT NULL DEFAULT 0,\n last_sync_at TEXT NOT NULL\n );\n `);\n\n await this.sqlite.execute(`\n CREATE TABLE IF NOT EXISTS _outbox_queue (\n id TEXT PRIMARY KEY,\n table_name TEXT NOT NULL,\n record_id TEXT NOT NULL,\n operation TEXT NOT NULL,\n payload TEXT NOT NULL,\n previous_state TEXT,\n status TEXT NOT NULL DEFAULT 'pending',\n error_message TEXT,\n retry_count INTEGER NOT NULL DEFAULT 0,\n created_at TEXT NOT NULL\n );\n `);\n\n await this.sqlite.execute(`\n CREATE TABLE IF NOT EXISTS _access_frequency (\n table_name TEXT PRIMARY KEY,\n read_count INTEGER NOT NULL DEFAULT 0,\n write_count INTEGER NOT NULL DEFAULT 0,\n last_accessed_at TEXT NOT NULL\n );\n `);\n }\n\n /**\n * Attaches window focus & network online hooks to trigger synchronization at user activity moments.\n */\n private attachLifecycleListeners(): void {\n const handleFocusOrOnline = () => {\n // 1. Flush any pending mutations\n this.processOutbox().catch(() => {});\n // 2. Sync frequently accessed & essential tables\n this.syncActiveTables().catch(() => {});\n };\n\n window.addEventListener('focus', handleFocusOrOnline);\n window.addEventListener('online', handleFocusOrOnline);\n\n this.cleanupListeners = () => {\n window.removeEventListener('focus', handleFocusOrOnline);\n window.removeEventListener('online', handleFocusOrOnline);\n };\n }\n\n /**\n * Increments a table read or write counter and records the current ISO timestamp.\n * @param collection - Table name recorded as a metadata value.\n * @param type - Counter to increment; read contributes weight 2 and write weight 5 in adaptive ranking.\n * @returns No value; resolves after the counter upsert.\n * @throws Rejects SQLite failures.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * await adapter.recordAccess(\"records\", \"read\");\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async recordAccess(collection: string, type: 'read' | 'write'): Promise<void> {\n const now = new Date().toISOString();\n const readIncrement = type === 'read' ? 1 : 0;\n const writeIncrement = type === 'write' ? 1 : 0;\n\n await this.sqlite.execute(\n `INSERT INTO _access_frequency (table_name, read_count, write_count, last_accessed_at)\n VALUES (?, ?, ?, ?)\n ON CONFLICT(table_name) DO UPDATE SET\n read_count = _access_frequency.read_count + excluded.read_count,\n write_count = _access_frequency.write_count + excluded.write_count,\n last_accessed_at = excluded.last_accessed_at`,\n [collection, readIncrement, writeIncrement, now]\n );\n }\n\n /**\n * Pulls every essential table plus the highest weighted access-frequency tables sequentially.\n * @param limit - Maximum frequency-ranked tables selected in addition to essential tables; defaults to 8 and is passed to SQLite without additional validation.\n * @returns Names whose pullChanges calls completed, including tables with no new rows; failed tables are warned and omitted.\n * @throws Rejects the initial frequency query failure; individual pull failures are caught.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * return await adapter.syncActiveTables(8);\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async syncActiveTables(limit: number = 8): Promise<string[]> {\n // 1. Get top accessed tables from frequency tracker\n const frequent = await this.sqlite.select<{ table_name: string }>(\n `SELECT table_name FROM _access_frequency ORDER BY (read_count * 2 + write_count * 5) DESC LIMIT ?`,\n [limit]\n );\n\n const targetTables = new Set<string>(this.essentialTables);\n frequent.forEach((row) => targetTables.add(row.table_name));\n\n const synced: string[] = [];\n for (const table of targetTables) {\n try {\n await this.pullChanges(table);\n synced.push(table);\n } catch (err) {\n console.warn(`[LocalFirst] Failed to sync active table '${table}':`, err);\n }\n }\n\n return synced;\n }\n\n /**\n * Queries local SQLite rows and schedules a best-effort read-frequency update.\n * @typeParam T - Expected local row shape; no runtime schema validation is performed.\n * @param collection - Local table identifier, quoted by the adapter.\n * @param filter - AND-combined equality/null or $eq/$gt/$gte/$lt/$lte column conditions; other operator objects are rejected.\n * @param options - Supports sort, limit, and skip; limit/skip must be nonnegative safe integers. Projection and other options are ignored.\n * @returns Local rows, or an empty array; remote freshness is not implied.\n * @throws Rejects unsupported filters, invalid pagination, or SQLite query failures. Access-stat failures are ignored.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * return await adapter.find(\"records\", { active: true }, { limit: 10 });\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async find<T = unknown>(collection: string, filter?: Filter, options?: QueryOptions): Promise<T[]> {\n this.recordAccess(collection, 'read').catch(() => {});\n const { sql, params } = this.buildSelectQuery(collection, filter, options);\n return await this.sqlite.select<T>(sql, params);\n }\n\n /**\n * Counts local rows matching a filter without pagination.\n * @param collection - Local table identifier, quoted by the adapter.\n * @param filter - Same supported AND-combined conditions as find.\n * @param options - Sort is retained; limit and skip are discarded. Other options are ignored.\n * @returns Numeric count, or zero if the driver returns no count row.\n * @throws Rejects unsupported filters or SQLite failures; access-stat failures are ignored.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * return await adapter.count(\"records\");\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async count(collection: string, filter?: Filter, options?: QueryOptions): Promise<number> {\n this.recordAccess(collection, 'read').catch(() => {});\n const { sql, params } = this.buildSelectQuery(collection, filter, { ...options, limit: undefined, skip: undefined });\n const rows = await this.sqlite.select<{ total: number }>(`SELECT COUNT(*) AS total FROM (${sql})`, params);\n return Number(rows[0]?.total ?? 0);\n }\n\n /**\n * Queries a local row by its id column.\n * @typeParam T - Expected local row shape; no runtime validation is performed.\n * @param collection - Local table identifier.\n * @param id - ID value bound to an equality filter.\n * @param options - find options with limit overridden to 1; skip, if supplied, still applies.\n * @returns First matching row, or null.\n * @throws Rejects invalid pagination or SQLite query failures.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * return await adapter.findOne(\"records\", \"record-1\");\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async findOne<T = unknown>(collection: string, id: string, options?: QueryOptions): Promise<T | null> {\n const rows = await this.find<T>(collection, { id: { $eq: id } }, { ...options, limit: 1 });\n return rows.length > 0 ? rows[0] : null;\n }\n\n /**\n * Inserts a local record before separately enqueueing its upstream INSERT.\n * @typeParam T - Expected returned record shape; no runtime validation is performed.\n * @param collection - Provisioned local table identifier.\n * @param data - Column values; a truthy id is retained, otherwise an ID is generated. Missing tenant columns may receive the configured tenantId. Object values are JSON-encoded for local SQL.\n * @param _options - Accepted for DataAccessAdapter compatibility and ignored.\n * @returns The input record with its chosen ID and optional tenant default, before upstream acknowledgement.\n * @throws Rejects local SQL, serialization, or enqueue failure; a local insert may already have committed. Background outbox errors are ignored by this call.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * return await adapter.create(\"records\", { title: \"Example\" });\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async create<T = unknown>(collection: string, data: Record<string, unknown>, _options?: QueryOptions): Promise<T> {\n this.recordAccess(collection, 'write').catch(() => {});\n\n const recordId = (data.id as string) || (typeof crypto !== 'undefined' ? crypto.randomUUID() : `rec_${Date.now()}`);\n const recordWithId: Record<string, any> = { ...data, id: recordId };\n if (this.tenantId && !recordWithId.tenant_id && !recordWithId.tenantId) {\n recordWithId.tenant_id = this.tenantId;\n }\n\n // 1. Write immediately to local SQLite\n await this.insertLocal(collection, recordWithId);\n\n // 2. Enqueue to Outbox\n await this.enqueueOutbox({\n id: typeof crypto !== 'undefined' ? crypto.randomUUID() : `out_${Date.now()}`,\n tableName: collection,\n recordId,\n operation: 'INSERT',\n payload: recordWithId,\n previousState: null,\n status: 'pending',\n retryCount: 0,\n createdAt: new Date().toISOString(),\n });\n\n // Trigger immediate background sync\n if (this.autoSyncOnMutation) {\n this.processOutbox().catch(() => {});\n }\n\n return recordWithId as T;\n }\n\n /**\n * Snapshots a local row, updates its columns, and separately enqueues an upstream UPDATE.\n * @typeParam T - Expected returned merged record shape.\n * @param collection - Provisioned local table identifier.\n * @param id - Record ID bound in local SQL and used for upstream update.\n * @param data - Updated columns; local SQL excludes id but the payload is forwarded unchanged upstream. An empty update can cause a SQLite syntax failure.\n * @param _options - Accepted for DataAccessAdapter compatibility and ignored.\n * @returns Snapshot merged with data and the supplied id, even if no local row existed.\n * @throws Rejects snapshot, local SQL, serialization, or enqueue failures; local changes may already have committed.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * return await adapter.update(\"records\", \"record-1\", { title: \"Changed\" });\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async update<T = unknown>(collection: string, id: string, data: Record<string, unknown>, _options?: QueryOptions): Promise<T> {\n this.recordAccess(collection, 'write').catch(() => {});\n\n // 1. Fetch current state as snapshot for rollback\n const existing = await this.findOne<Record<string, any>>(collection, id);\n\n // 2. Write immediately to local SQLite\n await this.updateLocal(collection, id, data);\n const updatedRecord = { ...(existing || {}), ...data, id };\n\n // 3. Enqueue to Outbox\n await this.enqueueOutbox({\n id: typeof crypto !== 'undefined' ? crypto.randomUUID() : `out_${Date.now()}`,\n tableName: collection,\n recordId: id,\n operation: 'UPDATE',\n payload: data,\n previousState: existing || null,\n status: 'pending',\n retryCount: 0,\n createdAt: new Date().toISOString(),\n });\n\n // Trigger background sync\n if (this.autoSyncOnMutation) {\n this.processOutbox().catch(() => {});\n }\n\n return updatedRecord as T;\n }\n\n /**\n * Deletes a local row and separately enqueues upstream deletion with a rollback snapshot.\n * @typeParam T - Expected deleted record shape.\n * @param collection - Provisioned local table identifier.\n * @param id - Record ID used for local and upstream deletion.\n * @param _options - Accepted for DataAccessAdapter compatibility and ignored.\n * @returns The previous local record, or an object containing only id when absent.\n * @throws Rejects snapshot, local delete, or enqueue failures; local deletion may already have committed.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * return await adapter.delete(\"records\", \"record-1\");\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async delete<T = unknown>(collection: string, id: string, _options?: QueryOptions): Promise<T> {\n this.recordAccess(collection, 'write').catch(() => {});\n const existing = await this.findOne<Record<string, any>>(collection, id);\n\n // 1. Delete from local SQLite\n await this.sqlite.execute(`DELETE FROM ${this.escapeId(collection)} WHERE id = ?`, [id]);\n\n // 2. Enqueue to Outbox\n await this.enqueueOutbox({\n id: typeof crypto !== 'undefined' ? crypto.randomUUID() : `out_${Date.now()}`,\n tableName: collection,\n recordId: id,\n operation: 'DELETE',\n payload: { id },\n previousState: existing || null,\n status: 'pending',\n retryCount: 0,\n createdAt: new Date().toISOString(),\n });\n\n // Trigger background sync\n if (this.autoSyncOnMutation) {\n this.processOutbox().catch(() => {});\n }\n\n return (existing || { id }) as T;\n }\n\n /**\n * Pulls incremental remote changes from Oracle ADB using ORA_ROWSCN cursor within a batch transaction.\n *\n * @param collection - Provisioned local table whose upstream supports ORA_ROWSCN filtering, ordering, and projection.\n * @returns Number of remote rows upserted, or zero when no changes were returned.\n * @throws Rejects upstream or local SQL failures; an opened local transaction is rolled back best-effort on upsert/cursor failure.\n * @remarks Remote deletes are not discovered by this pull. No per-table synchronization lock prevents overlapping pulls or concurrent host transactions.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * return await adapter.pullChanges(\"records\");\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async pullChanges(collection: string): Promise<number> {\n // 1. Get last synced SCN\n const meta = await this.sqlite.select<{ last_sync_scn: number }>(\n `SELECT last_sync_scn FROM _sync_meta WHERE table_name = ?`,\n [collection]\n );\n const lastScn = meta.length > 0 ? meta[0].last_sync_scn : 0;\n\n // 2. Query upstream with ORA_ROWSCN filter\n const filter = lastScn > 0 ? { ORA_ROWSCN: { $gt: lastScn } } : {};\n const remoteRecords = await this.upstream.find<Record<string, any>>(collection, filter, {\n sort: { ORA_ROWSCN: 1 },\n projection: ['*', 'ORA_ROWSCN'],\n });\n\n if (remoteRecords.length === 0) {\n return 0;\n }\n\n let maxScn = lastScn;\n\n // 3. Batch Transaction Upsert to avoid lock congestion\n await this.sqlite.execute(`BEGIN TRANSACTION;`);\n try {\n for (const record of remoteRecords) {\n const recordScn = Number(record.ORA_ROWSCN) || 0;\n if (recordScn > maxScn) {\n maxScn = recordScn;\n }\n await this.upsertLocal(collection, record);\n }\n\n // 4. Update sync meta cursor\n await this.sqlite.execute(\n `INSERT INTO _sync_meta (table_name, last_sync_scn, last_sync_at)\n VALUES (?, ?, ?)\n ON CONFLICT(table_name) DO UPDATE SET\n last_sync_scn = excluded.last_sync_scn,\n last_sync_at = excluded.last_sync_at`,\n [collection, maxScn, new Date().toISOString()]\n );\n\n await this.sqlite.execute(`COMMIT;`);\n } catch (txErr) {\n await this.sqlite.execute(`ROLLBACK;`).catch(() => {});\n throw txErr;\n }\n\n return remoteRecords.length;\n }\n\n /**\n * Background Outbox Worker:\n * Processes at most 20 pending mutations in creation order, pushes upstream, and attempts rollback for non-network failures.\n * @returns No value; resolves after one batch, or immediately if this instance already has a batch running.\n * @throws Rejects metadata reads, malformed persisted JSON, or error-handling SQL failures. Ordinary upstream failures are recorded or rolled back rather than rejected.\n * @remarks Recognized network failures return rows to pending and increment retry_count; no delay/backoff is applied here. Other failures attempt local snapshot rollback and mark failed; rollback/callback errors are logged. Remote success followed by local queue-delete failure may leave an uncertain delivery outcome. There is no cross-instance lease or deduplication.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * await adapter.processOutbox();\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n async processOutbox(): Promise<void> {\n if (this.isSyncing) return;\n this.isSyncing = true;\n\n try {\n const pendingItems = await this.sqlite.select<any>(\n `SELECT * FROM _outbox_queue WHERE status = 'pending' ORDER BY created_at ASC LIMIT 20`\n );\n\n for (const rawItem of pendingItems) {\n const item: OutboxItem = {\n id: rawItem.id,\n tableName: rawItem.table_name || rawItem.tableName,\n recordId: rawItem.record_id || rawItem.recordId,\n operation: rawItem.operation,\n payload: typeof rawItem.payload === 'string' ? JSON.parse(rawItem.payload) : rawItem.payload,\n previousState: rawItem.previous_state\n ? typeof rawItem.previous_state === 'string'\n ? JSON.parse(rawItem.previous_state)\n : rawItem.previous_state\n : null,\n status: rawItem.status,\n retryCount: rawItem.retry_count ?? rawItem.retryCount ?? 0,\n createdAt: rawItem.created_at || rawItem.createdAt,\n };\n\n try {\n await this.sqlite.execute(`UPDATE _outbox_queue SET status = 'syncing' WHERE id = ?`, [item.id]);\n\n // Push to Upstream Cloud DB (Oracle ADB / Supabase)\n if (item.operation === 'INSERT') {\n await this.upstream.create(item.tableName, item.payload);\n } else if (item.operation === 'UPDATE') {\n await this.upstream.update(item.tableName, item.recordId, item.payload);\n } else if (item.operation === 'DELETE') {\n await this.upstream.delete(item.tableName, item.recordId);\n }\n\n // Mark completed & clean up\n await this.sqlite.execute(`DELETE FROM _outbox_queue WHERE id = ?`, [item.id]);\n } catch (err: any) {\n const isNetworkError =\n err.name === 'AbortError' ||\n err.code === 'ECONNREFUSED' ||\n err.message?.includes('fetch failed') ||\n err.message?.includes('NetworkError');\n\n if (isNetworkError) {\n // Transient offline error: retry with backoff, DO NOT rollback\n await this.sqlite.execute(\n `UPDATE _outbox_queue SET status = 'pending', retry_count = retry_count + 1, error_message = ? WHERE id = ?`,\n [err.message, item.id]\n );\n } else {\n // Permanent error (business constraint / conflict / unauthorized)\n // Rollback local SQLite state using previousState snapshot!\n await this.rollbackItem(item, err);\n }\n }\n }\n } finally {\n this.isSyncing = false;\n }\n }\n\n /**\n * Rolls back a failed mutation in local SQLite using its previousState snapshot.\n */\n private async rollbackItem(item: OutboxItem, error: Error): Promise<void> {\n console.error(`[LocalFirst] Rolling back ${item.tableName}:${item.recordId} due to error:`, error);\n\n try {\n if (item.operation === 'INSERT') {\n // Rollback insert: delete the inserted record\n await this.sqlite.execute(`DELETE FROM ${this.escapeId(item.tableName)} WHERE id = ?`, [item.recordId]);\n } else if (item.operation === 'UPDATE' || item.operation === 'DELETE') {\n // Rollback update/delete: restore previousState snapshot\n if (item.previousState) {\n await this.upsertLocal(item.tableName, item.previousState);\n }\n }\n\n await this.sqlite.execute(`UPDATE _outbox_queue SET status = 'failed', error_message = ? WHERE id = ?`, [\n error.message,\n item.id,\n ]);\n\n if (this.onSyncError) {\n this.onSyncError({\n item,\n error,\n rolledBack: true,\n });\n }\n } catch (rbErr) {\n console.error('[LocalFirst] Critical: Failed to rollback record:', rbErr);\n }\n }\n\n private async enqueueOutbox(item: OutboxItem): Promise<void> {\n await this.sqlite.execute(\n `INSERT INTO _outbox_queue (id, table_name, record_id, operation, payload, previous_state, status, retry_count, created_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n [\n item.id,\n item.tableName,\n item.recordId,\n item.operation,\n JSON.stringify(item.payload),\n item.previousState ? JSON.stringify(item.previousState) : null,\n item.status,\n item.retryCount,\n item.createdAt,\n ]\n );\n }\n\n private async insertLocal(table: string, record: Record<string, any>): Promise<void> {\n const keys = Object.keys(record);\n const placeholders = keys.map(() => '?').join(', ');\n const values = keys.map((k) => (typeof record[k] === 'object' ? JSON.stringify(record[k]) : record[k]));\n const escapedKeys = keys.map((k) => this.escapeId(k)).join(', ');\n\n await this.sqlite.execute(\n `INSERT INTO ${this.escapeId(table)} (${escapedKeys}) VALUES (${placeholders})`,\n values\n );\n }\n\n private async updateLocal(table: string, id: string, data: Record<string, any>): Promise<void> {\n const keys = Object.keys(data).filter((k) => k !== 'id');\n const setClause = keys.map((k) => `${this.escapeId(k)} = ?`).join(', ');\n const values = keys.map((k) => (typeof data[k] === 'object' ? JSON.stringify(data[k]) : data[k]));\n\n await this.sqlite.execute(\n `UPDATE ${this.escapeId(table)} SET ${setClause} WHERE id = ?`,\n [...values, id]\n );\n }\n\n private async upsertLocal(table: string, record: Record<string, any>): Promise<void> {\n const keys = Object.keys(record);\n const placeholders = keys.map(() => '?').join(', ');\n const values = keys.map((k) => (typeof record[k] === 'object' ? JSON.stringify(record[k]) : record[k]));\n const escapedKeys = keys.map((k) => this.escapeId(k)).join(', ');\n\n const updateClauses = keys\n .filter((k) => k !== 'id')\n .map((k) => `${this.escapeId(k)} = excluded.${this.escapeId(k)}`)\n .join(', ');\n\n await this.sqlite.execute(\n `INSERT INTO ${this.escapeId(table)} (${escapedKeys}) VALUES (${placeholders})\n ON CONFLICT(id) DO UPDATE SET ${updateClauses}`,\n values\n );\n }\n\n private buildSelectQuery(table: string, filter?: Filter, options?: QueryOptions): { sql: string; params: any[] } {\n let sql = `SELECT * FROM ${this.escapeId(table)}`;\n const params: any[] = [];\n const whereParts: string[] = [];\n\n for (const value of [options?.limit, options?.skip]) if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) throw new Error('Invalid SQLite pagination');\n\n if (filter) {\n for (const [key, val] of Object.entries(filter)) {\n if (val !== null && typeof val === 'object' && !Array.isArray(val)) {\n const opObj = val as Record<string, any>;\n if ('$eq' in opObj) {\n whereParts.push(`${this.escapeId(key)} = ?`);\n params.push(opObj.$eq);\n } else if ('$gt' in opObj) {\n whereParts.push(`${this.escapeId(key)} > ?`);\n params.push(opObj.$gt);\n } else if ('$gte' in opObj) {\n whereParts.push(`${this.escapeId(key)} >= ?`);\n params.push(opObj.$gte);\n } else if ('$lt' in opObj) {\n whereParts.push(`${this.escapeId(key)} < ?`);\n params.push(opObj.$lt);\n } else if ('$lte' in opObj) {\n whereParts.push(`${this.escapeId(key)} <= ?`);\n params.push(opObj.$lte);\n } else { throw new Error('Unsupported SQLite filter'); }\n } else if (val === null) {\n whereParts.push(`${this.escapeId(key)} IS NULL`);\n } else {\n whereParts.push(`${this.escapeId(key)} = ?`);\n params.push(val);\n }\n }\n }\n\n if (whereParts.length > 0) {\n sql += ` WHERE ${whereParts.join(' AND ')}`;\n }\n\n if (options?.sort) {\n const orderParts = Object.entries(options.sort).map(\n ([col, dir]) => `${this.escapeId(col)} ${dir === -1 ? 'DESC' : 'ASC'}`\n );\n sql += ` ORDER BY ${orderParts.join(', ')}`;\n }\n\n if (options?.limit !== undefined || options?.skip !== undefined) {\n sql += ` LIMIT ${options.limit ?? -1}`;\n if (options?.skip) {\n sql += ` OFFSET ${options.skip}`;\n }\n }\n\n return { sql, params };\n }\n\n private escapeId(id: string): string {\n return `\"${id.replace(/\"/g, '\"\"')}\"`;\n }\n\n /**\n * Stops this instance's polling timer and removes its browser focus/online listeners.\n * @returns No value; repeated calls are safe. In-flight synchronization continues and drivers remain open.\n * @example\n * ```typescript\n * import { LocalFirstSqliteAdapter, type LocalFirstSqliteAdapterOptions } from \"@alneas/eaos-server/db/local-first-sqlite-adapter\";\n * export async function example(options: LocalFirstSqliteAdapterOptions) {\n * // The host supplies an initialized domain schema and authorized upstream adapter.\n * const adapter = new LocalFirstSqliteAdapter({ ...options, syncIntervalMs: 0, enableLifecycleSync: false });\n * try {\n * await adapter.initSchema();\n * adapter.destroy();\n * } finally { adapter.destroy(); }\n * }\n * ```\n */\n destroy(): void {\n if (this.syncTimer) {\n clearInterval(this.syncTimer);\n this.syncTimer = null;\n }\n if (this.cleanupListeners) {\n this.cleanupListeners();\n this.cleanupListeners = undefined;\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,102 @@
1
+ // src/agentic/graph-projection.ts
2
+ var GraphProjectionCoordinator = class {
3
+ now;
4
+ options;
5
+ /**
6
+ * Captures a host projection identity, adapter, cursor store, and optional clock.
7
+ * @param options - Host projection dependencies.
8
+ * @throws If projectionId is blank or adapter/cursorStore is absent.
9
+ */
10
+ constructor(options) {
11
+ if (!options?.projectionId?.trim() || !options.adapter || !options.cursorStore) throw new Error("Projection id, adapter and cursor store are required");
12
+ this.options = options;
13
+ this.now = options.now ?? (() => /* @__PURE__ */ new Date());
14
+ }
15
+ /**
16
+ * Applies one event and records projection progress, skipping recognized duplicate/stale events.
17
+ * @param event - Trusted workflow event envelope; the adapter receives a structured clone.
18
+ * @returns No value; resolves after application and cursor persistence or immediately for the last event ID or a nonincreasing revision of the last aggregate.
19
+ * @throws Rejects rebuilding state, cursor/clock/clone failures, or adapter failures. Adapter failure attempts a failed cursor write, which can itself replace the original error.
20
+ * @remarks A revision gap for the last aggregate increments lagCount and records lagging but still applies the event. Deduplication covers only the last event/aggregate cursor, not full history. Failure may occur after adapter side effects; no rollback or retry occurs here.
21
+ * @example
22
+ * ```typescript
23
+ * import { GraphProjectionCoordinator, type GraphProjectionCoordinatorOptions } from "@alneas/eaos-server/agentic/graph-projection";
24
+ * import type { WorkflowEventEnvelope } from "@alneas/eaos-core";
25
+ * export async function apply(options: GraphProjectionCoordinatorOptions, event: WorkflowEventEnvelope) {
26
+ * await new GraphProjectionCoordinator(options).apply(event);
27
+ * }
28
+ * ```
29
+ */
30
+ async apply(event) {
31
+ const current = await this.options.cursorStore.get(this.options.projectionId, event.tenantId);
32
+ if (current?.status === "rebuilding") throw new Error("Graph projection is rebuilding");
33
+ if (current?.lastEventId === event.eventId) return;
34
+ const sameAggregate = current?.lastAggregateType === event.aggregateType && current.lastAggregateId === event.aggregateId;
35
+ if (sameAggregate && current?.lastAggregateRevision !== void 0 && event.aggregateRevision <= current.lastAggregateRevision) return;
36
+ const detectedGap = sameAggregate && current?.lastAggregateRevision !== void 0 && event.aggregateRevision > current.lastAggregateRevision + 1;
37
+ const started = {
38
+ projectionId: this.options.projectionId,
39
+ tenantId: event.tenantId,
40
+ status: detectedGap ? "lagging" : "healthy",
41
+ lastEventId: current?.lastEventId,
42
+ lastAggregateRevision: current?.lastAggregateRevision,
43
+ lagCount: (current?.lagCount ?? 0) + (detectedGap ? 1 : 0),
44
+ updatedAt: this.now().toISOString()
45
+ };
46
+ await this.options.cursorStore.put(started);
47
+ try {
48
+ await this.options.adapter.apply(structuredClone(event));
49
+ await this.options.cursorStore.put({
50
+ ...started,
51
+ status: started.status,
52
+ lastEventId: event.eventId,
53
+ lastAggregateType: event.aggregateType,
54
+ lastAggregateId: event.aggregateId,
55
+ lastAggregateRevision: event.aggregateRevision,
56
+ updatedAt: this.now().toISOString(),
57
+ lastError: void 0
58
+ });
59
+ } catch (error) {
60
+ await this.options.cursorStore.put({
61
+ ...started,
62
+ status: "failed",
63
+ lagCount: started.lagCount + 1,
64
+ updatedAt: this.now().toISOString(),
65
+ lastError: error instanceof Error ? error.message.slice(0, 1e3) : "Graph projection failed"
66
+ });
67
+ throw error;
68
+ }
69
+ }
70
+ /**
71
+ * Marks a tenant projection rebuilding, delegates snapshot replacement, then records healthy or failed state.
72
+ * @param tenantId - Verified tenant whose relationship model is rebuilt.
73
+ * @param sourceRevision - Host source revision copied to the rebuilt cursor; not validated by this coordinator.
74
+ * @param snapshot - Host-authorized snapshot cloned before passing to the adapter.
75
+ * @returns No value; resolves after adapter rebuild and healthy cursor persistence.
76
+ * @throws Rejects clock, cursor, clone, or adapter failures; a failed-status write can replace an earlier failure.
77
+ * @remarks No cancellation or exclusive lock is acquired. The host must serialize rebuild/application and determine recovery after partial adapter or cursor failure.
78
+ * @example
79
+ * ```typescript
80
+ * import { GraphProjectionCoordinator, type GraphProjectionCoordinatorOptions } from "@alneas/eaos-server/agentic/graph-projection";
81
+ * export async function rebuild(options: GraphProjectionCoordinatorOptions, tenantId: string, revision: number, snapshot: unknown) {
82
+ * await new GraphProjectionCoordinator(options).rebuild(tenantId, revision, snapshot);
83
+ * }
84
+ * ```
85
+ */
86
+ async rebuild(tenantId, sourceRevision, snapshot) {
87
+ const now = this.now().toISOString();
88
+ await this.options.cursorStore.put({ projectionId: this.options.projectionId, tenantId, status: "rebuilding", lagCount: 0, updatedAt: now });
89
+ try {
90
+ await this.options.adapter.rebuild({ tenantId, sourceRevision, snapshot: structuredClone(snapshot) });
91
+ await this.options.cursorStore.put({ projectionId: this.options.projectionId, tenantId, status: "healthy", lastAggregateRevision: sourceRevision, lagCount: 0, updatedAt: this.now().toISOString() });
92
+ } catch (error) {
93
+ await this.options.cursorStore.put({ projectionId: this.options.projectionId, tenantId, status: "failed", lagCount: 1, updatedAt: this.now().toISOString(), lastError: error instanceof Error ? error.message.slice(0, 1e3) : "Graph rebuild failed" });
94
+ throw error;
95
+ }
96
+ }
97
+ };
98
+
99
+ export {
100
+ GraphProjectionCoordinator
101
+ };
102
+ //# sourceMappingURL=chunk-W6O3LVBY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/agentic/graph-projection.ts"],"sourcesContent":["import type { GraphProjectionAdapter, GraphProjectionCursor, WorkflowEventEnvelope } from '@alneas/eaos-core'\n\n/**\n * Host persistence for per-projection, per-tenant progress and failure state.\n * @see GraphProjectionCoordinator.\n */\nexport interface ProjectionCursorStore {\n /**\n * Reads current projection progress for a tenant.\n * @param projectionId - Host projection identifier.\n * @param tenantId - Verified event/snapshot tenant.\n * @returns Stored cursor, or null when absent.\n * @throws Store errors propagate to the coordinator.\n \n * @example\n * ```typescript\n * import type { ProjectionCursorStore } from \"@alneas/eaos-server\";\n * export async function inspectTenantProjection(\n * store: ProjectionCursorStore, projectionId: string, verifiedTenantId: string,\n * ) {\n * const cursor = await store.get(projectionId, verifiedTenantId);\n * return cursor?.status; // Null means no cursor was stored.\n * }\n * ```\n */\n get(projectionId: string, tenantId: string): Promise<GraphProjectionCursor | null>\n /**\n * Persists replacement projection progress.\n * @param cursor - Full progress/failure cursor to store.\n * @returns No value; resolves after persistence.\n * @throws Store errors propagate; the projection adapter may already have applied changes.\n \n * @example\n * ```typescript\n * import type { ProjectionCursorStore } from \"@alneas/eaos-server\";\n * export async function saveProjectionProgress(\n * store: ProjectionCursorStore, cursor: Parameters<ProjectionCursorStore[\"put\"]>[0],\n * ) {\n * // The host aligns this tenant cursor with committed projection work.\n * await store.put(cursor);\n * }\n * ```\n */\n put(cursor: GraphProjectionCursor): Promise<void>\n}\n\n/**\n * Host projection adapter, cursor persistence, and clock for {@link GraphProjectionCoordinator}.\n */\nexport interface GraphProjectionCoordinatorOptions {\n /** Nonblank projection identifier; retained exactly without trimming. */\n projectionId: string\n /** Host relationship-model adapter; must define idempotency and transaction policy. */\n adapter: GraphProjectionAdapter\n /** Host cursor persistence, separate from adapter transactions unless the host coordinates them. */\n cursorStore: ProjectionCursorStore\n /** Clock for ISO progress timestamps; defaults to a fresh Date. */\n now?: () => Date\n}\n\n/**\n * Applies domain events to an ADB Graph/PGQL or other relationship read model.\n *\n * The coordinator is intentionally independent of Oracle. The host supplies\n * the adapter and cursor store, so credentials, graph schema and transaction\n * policy never enter core/server.\n * @remarks Calls are not serialized by this class. The host must coordinate writers/rebuilds, authorize tenant data, and make adapter replay safe. Adapter changes and cursor writes are separate operations; a cursor failure can leave projection data applied but unacknowledged.\n * @example\n * ```typescript\n * import { GraphProjectionCoordinator, type GraphProjectionCoordinatorOptions } from \"@alneas/eaos-server/agentic/graph-projection\";\n * export function projection(options: GraphProjectionCoordinatorOptions) {\n * return new GraphProjectionCoordinator(options);\n * }\n * ```\n */\nexport class GraphProjectionCoordinator {\n private readonly now: () => Date\n private readonly options: GraphProjectionCoordinatorOptions\n\n /**\n * Captures a host projection identity, adapter, cursor store, and optional clock.\n * @param options - Host projection dependencies.\n * @throws If projectionId is blank or adapter/cursorStore is absent.\n */\n constructor(options: GraphProjectionCoordinatorOptions) {\n if (!options?.projectionId?.trim() || !options.adapter || !options.cursorStore) throw new Error('Projection id, adapter and cursor store are required')\n this.options = options\n this.now = options.now ?? (() => new Date())\n }\n\n /**\n * Applies one event and records projection progress, skipping recognized duplicate/stale events.\n * @param event - Trusted workflow event envelope; the adapter receives a structured clone.\n * @returns No value; resolves after application and cursor persistence or immediately for the last event ID or a nonincreasing revision of the last aggregate.\n * @throws Rejects rebuilding state, cursor/clock/clone failures, or adapter failures. Adapter failure attempts a failed cursor write, which can itself replace the original error.\n * @remarks A revision gap for the last aggregate increments lagCount and records lagging but still applies the event. Deduplication covers only the last event/aggregate cursor, not full history. Failure may occur after adapter side effects; no rollback or retry occurs here.\n * @example\n * ```typescript\n * import { GraphProjectionCoordinator, type GraphProjectionCoordinatorOptions } from \"@alneas/eaos-server/agentic/graph-projection\";\n * import type { WorkflowEventEnvelope } from \"@alneas/eaos-core\";\n * export async function apply(options: GraphProjectionCoordinatorOptions, event: WorkflowEventEnvelope) {\n * await new GraphProjectionCoordinator(options).apply(event);\n * }\n * ```\n */\n async apply(event: Readonly<WorkflowEventEnvelope>): Promise<void> {\n const current = await this.options.cursorStore.get(this.options.projectionId, event.tenantId)\n if (current?.status === 'rebuilding') throw new Error('Graph projection is rebuilding')\n if (current?.lastEventId === event.eventId) return\n const sameAggregate = current?.lastAggregateType === event.aggregateType && current.lastAggregateId === event.aggregateId\n if (sameAggregate && current?.lastAggregateRevision !== undefined && event.aggregateRevision <= current.lastAggregateRevision) return\n const detectedGap = sameAggregate && current?.lastAggregateRevision !== undefined && event.aggregateRevision > current.lastAggregateRevision + 1\n\n const started: GraphProjectionCursor = {\n projectionId: this.options.projectionId,\n tenantId: event.tenantId,\n status: detectedGap ? 'lagging' : 'healthy',\n lastEventId: current?.lastEventId,\n lastAggregateRevision: current?.lastAggregateRevision,\n lagCount: (current?.lagCount ?? 0) + (detectedGap ? 1 : 0),\n updatedAt: this.now().toISOString(),\n }\n await this.options.cursorStore.put(started)\n try {\n await this.options.adapter.apply(structuredClone(event))\n await this.options.cursorStore.put({\n ...started,\n status: started.status,\n lastEventId: event.eventId,\n lastAggregateType: event.aggregateType,\n lastAggregateId: event.aggregateId,\n lastAggregateRevision: event.aggregateRevision,\n updatedAt: this.now().toISOString(),\n lastError: undefined,\n })\n } catch (error) {\n await this.options.cursorStore.put({\n ...started,\n status: 'failed',\n lagCount: started.lagCount + 1,\n updatedAt: this.now().toISOString(),\n lastError: error instanceof Error ? error.message.slice(0, 1_000) : 'Graph projection failed',\n })\n throw error\n }\n }\n\n /**\n * Marks a tenant projection rebuilding, delegates snapshot replacement, then records healthy or failed state.\n * @param tenantId - Verified tenant whose relationship model is rebuilt.\n * @param sourceRevision - Host source revision copied to the rebuilt cursor; not validated by this coordinator.\n * @param snapshot - Host-authorized snapshot cloned before passing to the adapter.\n * @returns No value; resolves after adapter rebuild and healthy cursor persistence.\n * @throws Rejects clock, cursor, clone, or adapter failures; a failed-status write can replace an earlier failure.\n * @remarks No cancellation or exclusive lock is acquired. The host must serialize rebuild/application and determine recovery after partial adapter or cursor failure.\n * @example\n * ```typescript\n * import { GraphProjectionCoordinator, type GraphProjectionCoordinatorOptions } from \"@alneas/eaos-server/agentic/graph-projection\";\n * export async function rebuild(options: GraphProjectionCoordinatorOptions, tenantId: string, revision: number, snapshot: unknown) {\n * await new GraphProjectionCoordinator(options).rebuild(tenantId, revision, snapshot);\n * }\n * ```\n */\n async rebuild(tenantId: string, sourceRevision: number, snapshot: unknown): Promise<void> {\n const now = this.now().toISOString()\n await this.options.cursorStore.put({ projectionId: this.options.projectionId, tenantId, status: 'rebuilding', lagCount: 0, updatedAt: now })\n try {\n await this.options.adapter.rebuild({ tenantId, sourceRevision, snapshot: structuredClone(snapshot) })\n await this.options.cursorStore.put({ projectionId: this.options.projectionId, tenantId, status: 'healthy', lastAggregateRevision: sourceRevision, lagCount: 0, updatedAt: this.now().toISOString() })\n } catch (error) {\n await this.options.cursorStore.put({ projectionId: this.options.projectionId, tenantId, status: 'failed', lagCount: 1, updatedAt: this.now().toISOString(), lastError: error instanceof Error ? error.message.slice(0, 1_000) : 'Graph rebuild failed' })\n throw error\n }\n }\n}\n"],"mappings":"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","names":[]}
@@ -0,0 +1,47 @@
1
+ // src/workspace-context.ts
2
+ var WorkspaceContextError = class extends Error {
3
+ /**
4
+ * Creates an error with a stable status and caller-supplied message.
5
+ * @param status - 401 for missing identity, 403 for denied tenant access, or 503 for unavailable context data. This public readonly property is available for HTTP error mapping.
6
+ * @param message - Human-readable failure description.
7
+ */
8
+ constructor(status, message) {
9
+ super(message);
10
+ this.status = status;
11
+ }
12
+ status;
13
+ };
14
+ function createWorkspaceContextResolver(store) {
15
+ return async (identity, tenantId) => {
16
+ if (!identity.userId) throw new WorkspaceContextError(401, "Authentication required");
17
+ if (!tenantId) throw new WorkspaceContextError(403, "Tenant membership required");
18
+ try {
19
+ const tenant = await store.readTenant(tenantId);
20
+ if (!tenant) throw new WorkspaceContextError(403, "Tenant unavailable");
21
+ const member = await store.readMembership(tenantId, identity.userId);
22
+ const isSuperAdmin = identity.isSuperAdmin === true;
23
+ if (!member && !isSuperAdmin) throw new WorkspaceContextError(403, "Tenant membership required");
24
+ const selected = String(member?.role ?? (isSuperAdmin ? "admin" : "member")).trim();
25
+ const role = !selected || ["super_admin", "superadmin"].includes(selected.toLowerCase()) ? "member" : selected;
26
+ return {
27
+ userId: identity.userId,
28
+ tenantId,
29
+ email: identity.email ?? null,
30
+ tenantName: tenant.tenantName || tenantId,
31
+ industryType: tenant.industryType || "generic",
32
+ role,
33
+ roles: isSuperAdmin ? ["SUPER_ADMIN", "super_admin", "admin"] : ["USER", role],
34
+ isSuperAdmin
35
+ };
36
+ } catch (error) {
37
+ if (error instanceof WorkspaceContextError) throw error;
38
+ throw new WorkspaceContextError(503, "Tenant context unavailable");
39
+ }
40
+ };
41
+ }
42
+
43
+ export {
44
+ WorkspaceContextError,
45
+ createWorkspaceContextResolver
46
+ };
47
+ //# sourceMappingURL=chunk-WFEAKJLR.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/workspace-context.ts"],"sourcesContent":["/** Identity is supplied exclusively by the host's verified authentication middleware.\n * @see createWorkspaceContextResolver for host setup and usage.\n */\nexport interface WorkspaceIdentity {\n /** User ID established by verified host authentication. */\n userId: string\n /** Verified optional email; resolved contexts normalize absence to null. */\n email?: string | null\n /** Trusted platform authority; tenant roles cannot grant this privilege. Defaults to false. */\n isSuperAdmin?: boolean\n}\n/**\n * Host reads used by {@link createWorkspaceContextResolver}; filter inactive rows and apply schema policy in the adapter.\n */\nexport interface WorkspaceContextStore {\n /**\n * Loads the selected active tenant.\n * @param tenantId - Selected tenant ID from trusted routing.\n * @returns Active tenant display data, or undefined if unavailable.\n * @throws Persistence failures are wrapped as WorkspaceContextError with status 503.\n \n * @example\n * ```typescript\n * import type { WorkspaceContextStore } from \"@alneas/eaos-server\";\n * export async function readSelectedTenant(store: WorkspaceContextStore, trustedTenantId: string) {\n * // Host storage filters inactive tenants and enforces its schema/access policy.\n * const tenant = await store.readTenant(trustedTenantId);\n * return tenant?.tenantName;\n * }\n * ```\n */\n readTenant(tenantId: string): Promise<{ \n /** Display name of the selected active tenant; the context resolver falls back to tenantId when this value is empty. */\n tenantName: string; \n /** Optional tenant industry classification; the context resolver substitutes generic when absent or empty. */\n industryType?: string } | undefined>\n /**\n * Loads the active membership for this tenant/user pair.\n * @param tenantId - Selected active tenant ID.\n * @param userId - Authenticated user ID.\n * @returns Membership role, or undefined if absent; tenant roles never establish platform authority.\n * @throws Persistence failures are wrapped as WorkspaceContextError with status 503.\n \n * @example\n * ```typescript\n * import type { WorkspaceContextStore } from \"@alneas/eaos-server\";\n * export async function readVerifiedMembership(\n * store: WorkspaceContextStore, trustedTenantId: string, authenticatedUserId: string,\n * ) {\n * const membership = await store.readMembership(trustedTenantId, authenticatedUserId);\n * return membership?.role; // Absence must not grant tenant access.\n * }\n * ```\n */\n readMembership(tenantId: string, userId: string): Promise<{ \n /** Role from the active tenant/user membership; the resolver trims it and normalizes blank or super_admin/superadmin values to member rather than granting platform authority. */\n role: string } | undefined>\n}\n/**\n * Resolved user identity and authorized tenant context returned by {@link createWorkspaceContextResolver}.\n */\nexport interface WorkspaceContext {\n /** Authenticated user ID. */\n userId: string\n /** Authorized selected tenant ID. */\n tenantId: string\n /** Verified email, or null when absent. */\n email: string | null\n /** Tenant display name, falling back to tenantId for an empty stored name. */\n tenantName: string\n /** Tenant industry, falling back to \"generic\" for an empty stored value. */\n industryType: string\n /** Trimmed membership role, member for unsafe/blank roles, or admin for a platform administrator without membership. */\n role: string\n /** Platform administrators receive SUPER_ADMIN, super_admin, admin; other users receive USER and their normalized role. */\n roles: string[]\n /** True only when the verified identity explicitly has platform authority. */\n isSuperAdmin: boolean\n}\n/**\n * Reports an authentication, membership, or host-store failure using an HTTP-compatible status.\n * @see createWorkspaceContextResolver for the resolver that rejects with this error.\n * @example\n * ```typescript\n * import { WorkspaceContextError } from \"@alneas/eaos-server/workspace-context\";\n * const error = new WorkspaceContextError(403, \"Tenant membership required\");\n * console.log(error.status); // 403\n * ```\n */\nexport class WorkspaceContextError extends Error {\n /**\n * Creates an error with a stable status and caller-supplied message.\n * @param status - 401 for missing identity, 403 for denied tenant access, or 503 for unavailable context data. This public readonly property is available for HTTP error mapping.\n * @param message - Human-readable failure description.\n */\n constructor(public readonly status: 401 | 403 | 503, message: string) { super(message) }\n}\n/**\n * Builds a resolver that requires an existing tenant and membership or verified platform authority.\n * @param store - Host-owned active tenant and membership reads; see {@link WorkspaceContextStore}.\n * @returns An async resolver accepting verified identity and a selected tenant ID, resolving a fresh context. Missing email becomes null, tenantName falls back to tenant ID, and industryType falls back to \"generic\".\n * @throws The returned resolver rejects with WorkspaceContextError: 401 for empty userId, 403 for missing/unavailable tenant or denied membership, and 503 for other store failures.\n * @remarks The host supplies verified identity, schema, active-row filtering, and credentials. Reads are sequential and not wrapped in a transaction here. Platform administrators may select existing tenants without membership; membership role strings cannot grant platform authority. Blank or super_admin/superadmin membership roles normalize to member. This function writes no persistent data.\n * @example\n * ```typescript\n * import { createWorkspaceContextResolver, type WorkspaceContextStore, type WorkspaceIdentity } from \"@alneas/eaos-server/workspace-context\";\n * export async function selectedWorkspace(store: WorkspaceContextStore, identity: WorkspaceIdentity, tenantId: string) {\n * // The host implements active-row reads and authenticates identity before this call.\n * return createWorkspaceContextResolver(store)(identity, tenantId);\n * }\n * ```\n */\nexport function createWorkspaceContextResolver(store: WorkspaceContextStore) {\n return async (identity: WorkspaceIdentity, tenantId: string | undefined): Promise<WorkspaceContext> => {\n if (!identity.userId) throw new WorkspaceContextError(401, 'Authentication required')\n if (!tenantId) throw new WorkspaceContextError(403, 'Tenant membership required')\n try {\n const tenant = await store.readTenant(tenantId)\n if (!tenant) throw new WorkspaceContextError(403, 'Tenant unavailable')\n const member = await store.readMembership(tenantId, identity.userId)\n const isSuperAdmin = identity.isSuperAdmin === true\n if (!member && !isSuperAdmin) throw new WorkspaceContextError(403, 'Tenant membership required')\n // A tenant role never grants platform authority. Platform administrators may operate a selected tenant.\n const selected = String(member?.role ?? (isSuperAdmin ? 'admin' : 'member')).trim()\n const role = !selected || ['super_admin', 'superadmin'].includes(selected.toLowerCase()) ? 'member' : selected\n return { userId: identity.userId, tenantId, email: identity.email ?? null,\n tenantName: tenant.tenantName || tenantId, industryType: tenant.industryType || 'generic',\n role, roles: isSuperAdmin ? ['SUPER_ADMIN', 'super_admin', 'admin'] : ['USER', role], isSuperAdmin }\n } catch (error) {\n if (error instanceof WorkspaceContextError) throw error\n throw new WorkspaceContextError(503, 'Tenant context unavailable')\n }\n }\n}\n"],"mappings":";AAyFO,IAAM,wBAAN,cAAoC,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/C,YAA4B,QAAyB,SAAiB;AAAE,UAAM,OAAO;AAAzD;AAAA,EAA2D;AAAA,EAA3D;AAC9B;AAgBO,SAAS,+BAA+B,OAA8B;AAC3E,SAAO,OAAO,UAA6B,aAA4D;AACrG,QAAI,CAAC,SAAS,OAAQ,OAAM,IAAI,sBAAsB,KAAK,yBAAyB;AACpF,QAAI,CAAC,SAAU,OAAM,IAAI,sBAAsB,KAAK,4BAA4B;AAChF,QAAI;AACF,YAAM,SAAS,MAAM,MAAM,WAAW,QAAQ;AAC9C,UAAI,CAAC,OAAQ,OAAM,IAAI,sBAAsB,KAAK,oBAAoB;AACtE,YAAM,SAAS,MAAM,MAAM,eAAe,UAAU,SAAS,MAAM;AACnE,YAAM,eAAe,SAAS,iBAAiB;AAC/C,UAAI,CAAC,UAAU,CAAC,aAAc,OAAM,IAAI,sBAAsB,KAAK,4BAA4B;AAE/F,YAAM,WAAW,OAAO,QAAQ,SAAS,eAAe,UAAU,SAAS,EAAE,KAAK;AAClF,YAAM,OAAO,CAAC,YAAY,CAAC,eAAe,YAAY,EAAE,SAAS,SAAS,YAAY,CAAC,IAAI,WAAW;AACtG,aAAO;AAAA,QAAE,QAAQ,SAAS;AAAA,QAAQ;AAAA,QAAU,OAAO,SAAS,SAAS;AAAA,QACnE,YAAY,OAAO,cAAc;AAAA,QAAU,cAAc,OAAO,gBAAgB;AAAA,QAChF;AAAA,QAAM,OAAO,eAAe,CAAC,eAAe,eAAe,OAAO,IAAI,CAAC,QAAQ,IAAI;AAAA,QAAG;AAAA,MAAa;AAAA,IACvG,SAAS,OAAO;AACd,UAAI,iBAAiB,sBAAuB,OAAM;AAClD,YAAM,IAAI,sBAAsB,KAAK,4BAA4B;AAAA,IACnE;AAAA,EACF;AACF;","names":[]}
@@ -0,0 +1,67 @@
1
+ // src/middleware/auth.ts
2
+ import { createMiddleware } from "hono/factory";
3
+ function createAuthMiddleware(options) {
4
+ return createMiddleware(async (c, next) => {
5
+ const path = c.req.path;
6
+ const skipPaths = options?.skipPaths || ["/health", "/metrics", "/api/health"];
7
+ if (skipPaths.some((p) => path.startsWith(p))) {
8
+ return await next();
9
+ }
10
+ if (c.get("tenantId") && c.get("userId")) {
11
+ return await next();
12
+ }
13
+ if (!options?.verifier) {
14
+ return await next();
15
+ }
16
+ const authHeader = c.req.header("Authorization");
17
+ const cookieHeader = c.req.header("Cookie");
18
+ const sessionResult = await options.verifier.verifySession(authHeader, cookieHeader);
19
+ if (sessionResult.pendingApproval) {
20
+ return c.json({
21
+ error: "\u30A2\u30AB\u30A6\u30F3\u30C8\u306F\u627F\u8A8D\u5F85\u3061\u3067\u3059\u3002\u7BA1\u7406\u8005\u306B\u9023\u7D61\u3057\u3066\u304F\u3060\u3055\u3044\u3002",
22
+ code: "PENDING_APPROVAL",
23
+ /** Optional approval reference included in a pending response. */
24
+ pendingId: sessionResult.pendingId
25
+ }, 403);
26
+ }
27
+ if (sessionResult.valid) {
28
+ let tenantId = sessionResult.tenantId;
29
+ const xTenantId = c.req.header("X-Tenant-Id");
30
+ if (xTenantId && sessionResult.isSuperAdmin) {
31
+ tenantId = xTenantId;
32
+ }
33
+ if (tenantId) c.set("tenantId", tenantId);
34
+ if (sessionResult.userId) c.set("userId", sessionResult.userId);
35
+ if (sessionResult.user) {
36
+ c.set("user", sessionResult.user);
37
+ } else if (sessionResult.userId) {
38
+ c.set("user", {
39
+ id: sessionResult.userId,
40
+ /** Optional verified email used when synthesizing a user object. */
41
+ email: sessionResult.email || "",
42
+ /** Optional verified tenant claim; session middleware can continue without it, so downstream tenant admission remains required. */
43
+ tenantId: tenantId || "",
44
+ roles: sessionResult.isSuperAdmin ? ["super_admin"] : ["user"]
45
+ });
46
+ }
47
+ return await next();
48
+ }
49
+ if (authHeader && options.verifier.verifyApiKey) {
50
+ const token = authHeader.replace(/^Bearer\s+/i, "");
51
+ if (token) {
52
+ const apiKeyResult = await options.verifier.verifyApiKey(token);
53
+ if (apiKeyResult.valid && apiKeyResult.tenantId) {
54
+ c.set("tenantId", apiKeyResult.tenantId);
55
+ if (apiKeyResult.userId) c.set("userId", apiKeyResult.userId);
56
+ return await next();
57
+ }
58
+ }
59
+ }
60
+ return c.json({ error: "Unauthorized" }, 401);
61
+ });
62
+ }
63
+
64
+ export {
65
+ createAuthMiddleware
66
+ };
67
+ //# sourceMappingURL=chunk-XIEY2OF4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/middleware/auth.ts"],"sourcesContent":["import { createMiddleware } from 'hono/factory'\nimport type { EaosEnv, EaosUser } from '@alneas/eaos-core'\n\n/**\n * Host authentication adapters used by {@link createAuthMiddleware}.\n * @remarks Results must come from verified credentials and trusted membership data; this middleware does not independently validate claims.\n */\nexport interface AuthSessionVerifier {\n /**\n * Verifies a session before the optional API key fallback.\n * @param authHeader - Raw Authorization header, or undefined if absent.\n * @param cookieHeader - Raw Cookie header, or undefined if absent.\n * @returns Session validity and trusted optional identity claims. pendingApproval takes priority over valid and produces 403.\n * @throws Rejections propagate through request handling.\n \n * @example\n * ```typescript\n * import type { AuthSessionVerifier } from \"@alneas/eaos-server\";\n * export async function inspectSession(verifier: AuthSessionVerifier, request: Request) {\n * // The host verifier checks real credentials; a header alone is not trusted.\n * const result = await verifier.verifySession(\n * request.headers.get(\"Authorization\") ?? undefined,\n * request.headers.get(\"Cookie\") ?? undefined,\n * );\n * return { valid: result.valid, pendingApproval: result.pendingApproval === true };\n * }\n * ```\n */\n verifySession(authHeader?: string, cookieHeader?: string): Promise<{\n /** True only after successful trusted credential verification. */\n valid: boolean\n /** Optional authenticated user ID copied to Hono context. */\n userId?: string\n /** Optional verified email used when synthesizing a user object. */\n email?: string\n /** Optional verified tenant claim; session middleware can continue without it, so downstream tenant admission remains required. */\n tenantId?: string\n /** Trusted platform authority enabling X-Tenant-Id selection and synthesized super_admin role. */\n isSuperAdmin?: boolean\n /** When truthy, produces a 403 before session validity or API-key fallback is considered. */\n pendingApproval?: boolean\n /** Optional approval reference included in a pending response. */\n pendingId?: string\n /** Optional trusted user object copied unchanged; if omitted with userId, middleware synthesizes one. */\n user?: EaosUser\n }>\n /**\n * Verifies a nonempty credential after session verification fails.\n * @param token - Authorization value with a leading Bearer prefix removed case-insensitively.\n * @returns Validity and trusted identity claims; a valid result also needs tenantId to grant access.\n * @throws Adapter errors propagate through request handling.\n \n * @example\n * ```typescript\n * import type { AuthSessionVerifier } from \"@alneas/eaos-server\";\n * export async function inspectApiKey(verifier: AuthSessionVerifier, token: string) {\n * if (!verifier.verifyApiKey) throw new Error(\"Host API-key verification is unavailable\");\n * // Supply the actual credential without logging or persisting it here.\n * const result = await verifier.verifyApiKey(token);\n * return result.valid && !!result.tenantId;\n * }\n * ```\n */\n verifyApiKey?(token: string): Promise<{\n /** True for a verified API key; tenantId is additionally required to continue. */\n valid: boolean\n /** Required tenant claim for a successful middleware API-key fallback. */\n tenantId?: string\n /** Optional authenticated key owner copied to userId; no user object is synthesized for this path. */\n userId?: string\n }> | {\n \n /**\n * True only when the host has verified the presented API key; middleware also requires a truthy tenantId before continuing.\n */\n valid: boolean\n \n /**\n * Trusted tenant claim required for successful API-key fallback; middleware does not verify tenant membership independently.\n */\n tenantId?: string\n \n /**\n * Optional verified key-owner identity copied to request context; this fallback does not synthesize an EaosUser object.\n */\n userId?: string\n }\n}\n\n/**\n * Authentication configuration for {@link createAuthMiddleware}.\n */\nexport interface AuthMiddlewareOptions {\n /** Host verifier; omission allows all requests through without establishing identity. */\n verifier?: AuthSessionVerifier\n /** Case-sensitive path prefixes exempt from verification; defaults to /health, /metrics, and /api/health. An empty array disables these exemptions. */\n skipPaths?: string[]\n}\n\n/**\n * Resolves trusted session or API key claims into Hono context and rejects invalid credentials.\n * @param options - Host verifier and excluded path prefixes; see {@link AuthMiddlewareOptions}.\n * @returns Middleware that continues exempt or authenticated requests, returns 403 for pending approval, and otherwise returns 401.\n * @throws Request execution propagates verifier and downstream middleware errors.\n * @remarks Requests with both userId and tenantId already in context bypass verification. Omitting a verifier also bypasses authentication. Only session results marked isSuperAdmin may select X-Tenant-Id; the host remains responsible for tenant existence and authorization. A synthesized user gets super_admin or user roles; API key fallback sets no user object.\n * @example\n * ```typescript\n * import { Hono } from \"hono\";\n * import type { EaosEnv } from \"@alneas/eaos-core\";\n * import { createAuthMiddleware, type AuthSessionVerifier } from \"@alneas/eaos-server/middleware/auth\";\n * export function authenticatedApp(verifier: AuthSessionVerifier) {\n * // Supply a host adapter that actually validates sessions and tenant claims.\n * const app = new Hono<EaosEnv>();\n * app.use(\"*\", createAuthMiddleware({ verifier, skipPaths: [] }));\n * app.get(\"/identity\", c => c.json({ userId: c.get(\"userId\") }));\n * return app;\n * }\n * ```\n */\nexport function createAuthMiddleware(options?: AuthMiddlewareOptions) {\n return createMiddleware<EaosEnv>(async (c, next) => {\n const path = c.req.path\n const skipPaths = options?.skipPaths || ['/health', '/metrics', '/api/health']\n\n if (skipPaths.some((p) => path.startsWith(p))) {\n return await next()\n }\n\n // 既に tenantId がセットされている場合(API Gateway 等で解決済み)\n if (c.get('tenantId') && c.get('userId')) {\n return await next()\n }\n\n if (!options?.verifier) {\n // 検証器が設定されていない場合はスルー(開発用または後続ミドルウェアに委譲)\n return await next()\n }\n\n const authHeader = c.req.header('Authorization')\n const cookieHeader = c.req.header('Cookie')\n\n const sessionResult = await options.verifier.verifySession(authHeader, cookieHeader)\n\n if (sessionResult.pendingApproval) {\n return c.json({\n error: 'アカウントは承認待ちです。管理者に連絡してください。',\n code: 'PENDING_APPROVAL',\n /** Optional approval reference included in a pending response. */\n pendingId: sessionResult.pendingId,\n }, 403)\n }\n\n if (sessionResult.valid) {\n let tenantId = sessionResult.tenantId\n\n // X-Tenant-Id によるテナント切替(スーパー管理者のみ)\n const xTenantId = c.req.header('X-Tenant-Id')\n if (xTenantId && sessionResult.isSuperAdmin) {\n tenantId = xTenantId\n }\n\n if (tenantId) c.set('tenantId', tenantId)\n if (sessionResult.userId) c.set('userId', sessionResult.userId)\n if (sessionResult.user) {\n c.set('user', sessionResult.user)\n } else if (sessionResult.userId) {\n c.set('user', {\n id: sessionResult.userId,\n /** Optional verified email used when synthesizing a user object. */\n email: sessionResult.email || '',\n /** Optional verified tenant claim; session middleware can continue without it, so downstream tenant admission remains required. */\n tenantId: tenantId || '',\n roles: sessionResult.isSuperAdmin ? ['super_admin'] : ['user'],\n })\n }\n return await next()\n }\n\n // API Key フォールバック検証\n if (authHeader && options.verifier.verifyApiKey) {\n const token = authHeader.replace(/^Bearer\\s+/i, '')\n if (token) {\n const apiKeyResult = await options.verifier.verifyApiKey(token)\n if (apiKeyResult.valid && apiKeyResult.tenantId) {\n c.set('tenantId', apiKeyResult.tenantId)\n if (apiKeyResult.userId) c.set('userId', apiKeyResult.userId)\n return await next()\n }\n }\n }\n\n return c.json({ error: 'Unauthorized' }, 401)\n })\n}\n"],"mappings":";AAAA,SAAS,wBAAwB;AAuH1B,SAAS,qBAAqB,SAAiC;AACpE,SAAO,iBAA0B,OAAO,GAAG,SAAS;AAClD,UAAM,OAAO,EAAE,IAAI;AACnB,UAAM,YAAY,SAAS,aAAa,CAAC,WAAW,YAAY,aAAa;AAE7E,QAAI,UAAU,KAAK,CAAC,MAAM,KAAK,WAAW,CAAC,CAAC,GAAG;AAC7C,aAAO,MAAM,KAAK;AAAA,IACpB;AAGA,QAAI,EAAE,IAAI,UAAU,KAAK,EAAE,IAAI,QAAQ,GAAG;AACxC,aAAO,MAAM,KAAK;AAAA,IACpB;AAEA,QAAI,CAAC,SAAS,UAAU;AAEtB,aAAO,MAAM,KAAK;AAAA,IACpB;AAEA,UAAM,aAAa,EAAE,IAAI,OAAO,eAAe;AAC/C,UAAM,eAAe,EAAE,IAAI,OAAO,QAAQ;AAE1C,UAAM,gBAAgB,MAAM,QAAQ,SAAS,cAAc,YAAY,YAAY;AAEnF,QAAI,cAAc,iBAAiB;AACjC,aAAO,EAAE,KAAK;AAAA,QACZ,OAAO;AAAA,QACP,MAAM;AAAA;AAAA,QAEV,WAAW,cAAc;AAAA,MACvB,GAAG,GAAG;AAAA,IACR;AAEA,QAAI,cAAc,OAAO;AACvB,UAAI,WAAW,cAAc;AAG7B,YAAM,YAAY,EAAE,IAAI,OAAO,aAAa;AAC5C,UAAI,aAAa,cAAc,cAAc;AAC3C,mBAAW;AAAA,MACb;AAEA,UAAI,SAAU,GAAE,IAAI,YAAY,QAAQ;AACxC,UAAI,cAAc,OAAQ,GAAE,IAAI,UAAU,cAAc,MAAM;AAC9D,UAAI,cAAc,MAAM;AACtB,UAAE,IAAI,QAAQ,cAAc,IAAI;AAAA,MAClC,WAAW,cAAc,QAAQ;AAC/B,UAAE,IAAI,QAAQ;AAAA,UACZ,IAAI,cAAc;AAAA;AAAA,UAExB,OAAO,cAAc,SAAS;AAAA;AAAA,UAE9B,UAAU,YAAY;AAAA,UAChB,OAAO,cAAc,eAAe,CAAC,aAAa,IAAI,CAAC,MAAM;AAAA,QAC/D,CAAC;AAAA,MACH;AACA,aAAO,MAAM,KAAK;AAAA,IACpB;AAGA,QAAI,cAAc,QAAQ,SAAS,cAAc;AAC/C,YAAM,QAAQ,WAAW,QAAQ,eAAe,EAAE;AAClD,UAAI,OAAO;AACT,cAAM,eAAe,MAAM,QAAQ,SAAS,aAAa,KAAK;AAC9D,YAAI,aAAa,SAAS,aAAa,UAAU;AAC/C,YAAE,IAAI,YAAY,aAAa,QAAQ;AACvC,cAAI,aAAa,OAAQ,GAAE,IAAI,UAAU,aAAa,MAAM;AAC5D,iBAAO,MAAM,KAAK;AAAA,QACpB;AAAA,MACF;AAAA,IACF;AAEA,WAAO,EAAE,KAAK,EAAE,OAAO,eAAe,GAAG,GAAG;AAAA,EAC9C,CAAC;AACH;","names":[]}
@@ -0,0 +1,37 @@
1
+ // src/lifecycle-engine/platform/user-directory.ts
2
+ var personKinds = ["employee", "contractor", "partner_contact", "customer_contact", "other"];
3
+ function parseAccountMemberships(value) {
4
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Invalid account memberships");
5
+ const input = value;
6
+ if (!Array.isArray(input.memberships) || input.memberships.length > 1e3) throw new Error("Invalid account memberships");
7
+ const seen = /* @__PURE__ */ new Set();
8
+ const memberships = input.memberships.map((item) => {
9
+ if (!item || typeof item !== "object" || Array.isArray(item)) throw new Error("Invalid membership");
10
+ const { tenantId, role, active } = item;
11
+ if (typeof tenantId !== "string" || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,49}$/.test(tenantId) || typeof role !== "string" || !/^[a-zA-Z][a-zA-Z0-9_-]{0,49}$/.test(role) || ["superadmin", "super_admin", "platform_admin"].includes(role.toLowerCase()) || typeof active !== "boolean" || seen.has(tenantId)) throw new Error("Invalid membership");
12
+ seen.add(tenantId);
13
+ return { tenantId, role, active };
14
+ });
15
+ const defaultTenantId = input.defaultTenantId;
16
+ if (defaultTenantId !== void 0 && (typeof defaultTenantId !== "string" || !memberships.some((m) => m.tenantId === defaultTenantId && m.active))) throw new Error("Invalid default tenant");
17
+ return { memberships, ...typeof defaultTenantId === "string" ? { defaultTenantId } : {} };
18
+ }
19
+ function selectAccountTenant(account) {
20
+ const active = account.memberships.filter((m) => m.active);
21
+ return account.defaultTenantId ?? (active.length === 1 ? active[0]?.tenantId : void 0);
22
+ }
23
+ function initialPersonName(explicitName, verifiedDisplayName) {
24
+ const explicit = typeof explicitName === "string" ? explicitName.trim() : "";
25
+ const display = typeof verifiedDisplayName === "string" ? verifiedDisplayName.trim() : "";
26
+ const name = explicit || display;
27
+ if (!name || name.length > 200) throw new Error("Person name required (maximum 200 characters)");
28
+ return name;
29
+ }
30
+
31
+ export {
32
+ personKinds,
33
+ parseAccountMemberships,
34
+ selectAccountTenant,
35
+ initialPersonName
36
+ };
37
+ //# sourceMappingURL=chunk-XITLHDG3.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lifecycle-engine/platform/user-directory.ts"],"sourcesContent":["/**\n * Host-verified tenant membership used for authorization, separate from business person records.\n * \n * \n * Account membership is authorization data; business people do not grant access.\n * @see parseAccountMemberships for canonical integration usage.\n */\nexport interface AccountMembership {\n /**\n * Concrete tenant ID, at most 50 supported identifier characters.\n */\n tenantId: string;\n /**\n * Tenant-scoped role, at most 50 characters; platform-global role names are forbidden.\n */\n role: string;\n /**\n * Whether the membership currently grants tenant admission.\n */\n active: boolean }\n/**\n * Validated tenant memberships with optional explicit active default selection.\n * \n * @see parseAccountMemberships for canonical integration usage.\n */\nexport interface AccountMemberships {\n /**\n * At most 1000 distinct tenant grants; empty means no tenant access.\n */\n memberships: readonly AccountMembership[];\n /**\n * Optional tenant among active memberships; omitted requires selection when multiple are active.\n */\n defaultTenantId?: string }\n/**\n * Supported business directory person categories; categories never grant account membership.\n * \n * @see parseAccountMemberships for canonical integration usage.\n */\nexport const personKinds = ['employee', 'contractor', 'partner_contact', 'customer_contact', 'other'] as const\n/**\n * One supported business relationship category from personKinds.\n * \n * @see parseAccountMemberships for canonical integration usage.\n */\nexport type PersonKind = typeof personKinds[number]\n/**\n * Tenant-scoped business contact/person metadata that never grants authenticated membership.\n * \n * @see parseAccountMemberships for canonical integration usage.\n */\nexport interface DirectoryPerson {\n /**\n * Business person record ID.\n */\n id: string\n /**\n * Tenant owning this directory record.\n */\n tenantId: string\n /**\n * Optional link to a host identity; linkage alone grants no access.\n */\n identityId?: string\n /**\n * Stored display/legal name from explicit or verified creation inputs.\n */\n name: string\n /**\n * Optional contact address; never used to manufacture a name.\n */\n email?: string\n /**\n * Optional organization display text.\n */\n organizationName?: string\n /**\n * Business relationship categories, independent of account roles.\n */\n kinds: readonly PersonKind[]\n /**\n * Business directory status, independent of login authorization.\n */\n active: boolean\n}\n\n/**\n * Normalizes bounded tenant memberships while rejecting duplicate tenant grants and platform-wide roles.\n * \n * Fail closed on malformed/duplicate membership data; provider metadata is host-owned.\n * @param value - Host-owned verified provider metadata with at most 1000 membership entries and optional active defaultTenantId.\n * @returns New membership records and an optional default tenant; unknown metadata fields are omitted.\n * @throws Error for malformed IDs/roles, duplicate tenants, nonboolean active flags, forbidden global roles or a default outside active memberships.\n * \n * @remarks\n * Tenant roles do not grant platform administration. Parsing validates shape, not metadata authenticity; business directory people never establish access.\n * \n * @example\n * ```typescript\n * import { parseAccountMemberships } from '@alneas/eaos-server/platform/user-directory';\n * const account = parseAccountMemberships({ memberships: [{ tenantId: 'sample', role: 'member', active: true }], defaultTenantId: 'sample' });\n * ```\n */\nexport function parseAccountMemberships(value: unknown): AccountMemberships {\n if (!value || typeof value !== 'object' || Array.isArray(value)) throw new Error('Invalid account memberships')\n const input = value as Record<string, unknown>\n if (!Array.isArray(input.memberships) || input.memberships.length > 1000) throw new Error('Invalid account memberships')\n const seen = new Set<string>()\n const memberships = input.memberships.map((item): AccountMembership => {\n if (!item || typeof item !== 'object' || Array.isArray(item)) throw new Error('Invalid membership')\n const { tenantId, role, active } = item as Record<string, unknown>\n if (typeof tenantId !== 'string' || !/^[a-zA-Z0-9][a-zA-Z0-9_.-]{0,49}$/.test(tenantId)\n || typeof role !== 'string' || !/^[a-zA-Z][a-zA-Z0-9_-]{0,49}$/.test(role)\n || ['superadmin', 'super_admin', 'platform_admin'].includes(role.toLowerCase())\n || typeof active !== 'boolean' || seen.has(tenantId)) throw new Error('Invalid membership')\n seen.add(tenantId)\n return { tenantId, role, active }\n })\n const defaultTenantId = input.defaultTenantId\n if (defaultTenantId !== undefined && (typeof defaultTenantId !== 'string' || !memberships.some(m => m.tenantId === defaultTenantId && m.active))) throw new Error('Invalid default tenant')\n return { memberships, ...(typeof defaultTenantId === 'string' ? { defaultTenantId } : {}) }\n}\n/**\n * Selects an account's declared default or its only active membership without choosing among multiple active tenants.\n * @param account - Previously parsed AccountMemberships; this helper does not revalidate the default.\n * @returns The default tenant, the sole active tenant, or undefined when explicit selection is needed/no active membership exists.\n * \n * @example\n * ```typescript\n * import { parseAccountMemberships, selectAccountTenant } from '@alneas/eaos-server/platform/user-directory';\n * const tenantId = selectAccountTenant(parseAccountMemberships({ memberships: [{ tenantId: 'sample', role: 'member', active: true }] }));\n * ```\n */\nexport function selectAccountTenant(account: AccountMemberships): string | undefined {\n const active = account.memberships.filter(m => m.active)\n return account.defaultTenantId ?? (active.length === 1 ? active[0]?.tenantId : undefined)\n}\n/**\n * Chooses a trimmed explicit name or verified display name for initial person creation.\n * \n * Only at initial creation: require a real name, never manufacture one from email.\n * @param explicitName - User-supplied name; nonstring/blank values fall back to verified display name.\n * @param verifiedDisplayName - Trusted provider display name; email addresses are not synthesized into names.\n * @returns The chosen trimmed nonblank name, at most 200 characters.\n * @throws Error when both names are blank/nonstring or the chosen name exceeds 200 characters.\n * \n * @remarks\n * Use only at initial registration; this helper does not update existing profiles or validate legal identity.\n * \n * @example\n * ```typescript\n * import { initialPersonName } from '@alneas/eaos-server/platform/user-directory';\n * const name = initialPersonName('Sample Person', undefined);\n * ```\n */\nexport function initialPersonName(explicitName: unknown, verifiedDisplayName: unknown): string {\n const explicit = typeof explicitName === 'string' ? explicitName.trim() : ''\n const display = typeof verifiedDisplayName === 'string' ? verifiedDisplayName.trim() : ''\n const name = explicit || display\n if (!name || name.length > 200) throw new Error('Person name required (maximum 200 characters)')\n return name\n}\n"],"mappings":";AAuCO,IAAM,cAAc,CAAC,YAAY,cAAc,mBAAmB,oBAAoB,OAAO;AAgE7F,SAAS,wBAAwB,OAAoC;AAC1E,MAAI,CAAC,SAAS,OAAO,UAAU,YAAY,MAAM,QAAQ,KAAK,EAAG,OAAM,IAAI,MAAM,6BAA6B;AAC9G,QAAM,QAAQ;AACd,MAAI,CAAC,MAAM,QAAQ,MAAM,WAAW,KAAK,MAAM,YAAY,SAAS,IAAM,OAAM,IAAI,MAAM,6BAA6B;AACvH,QAAM,OAAO,oBAAI,IAAY;AAC7B,QAAM,cAAc,MAAM,YAAY,IAAI,CAAC,SAA4B;AACrE,QAAI,CAAC,QAAQ,OAAO,SAAS,YAAY,MAAM,QAAQ,IAAI,EAAG,OAAM,IAAI,MAAM,oBAAoB;AAClG,UAAM,EAAE,UAAU,MAAM,OAAO,IAAI;AACnC,QAAI,OAAO,aAAa,YAAY,CAAC,oCAAoC,KAAK,QAAQ,KACjF,OAAO,SAAS,YAAY,CAAC,gCAAgC,KAAK,IAAI,KACtE,CAAC,cAAc,eAAe,gBAAgB,EAAE,SAAS,KAAK,YAAY,CAAC,KAC3E,OAAO,WAAW,aAAa,KAAK,IAAI,QAAQ,EAAG,OAAM,IAAI,MAAM,oBAAoB;AAC5F,SAAK,IAAI,QAAQ;AACjB,WAAO,EAAE,UAAU,MAAM,OAAO;AAAA,EAClC,CAAC;AACD,QAAM,kBAAkB,MAAM;AAC9B,MAAI,oBAAoB,WAAc,OAAO,oBAAoB,YAAY,CAAC,YAAY,KAAK,OAAK,EAAE,aAAa,mBAAmB,EAAE,MAAM,GAAI,OAAM,IAAI,MAAM,wBAAwB;AAC1L,SAAO,EAAE,aAAa,GAAI,OAAO,oBAAoB,WAAW,EAAE,gBAAgB,IAAI,CAAC,EAAG;AAC5F;AAYO,SAAS,oBAAoB,SAAiD;AACnF,QAAM,SAAS,QAAQ,YAAY,OAAO,OAAK,EAAE,MAAM;AACvD,SAAO,QAAQ,oBAAoB,OAAO,WAAW,IAAI,OAAO,CAAC,GAAG,WAAW;AACjF;AAmBO,SAAS,kBAAkB,cAAuB,qBAAsC;AAC7F,QAAM,WAAW,OAAO,iBAAiB,WAAW,aAAa,KAAK,IAAI;AAC1E,QAAM,UAAU,OAAO,wBAAwB,WAAW,oBAAoB,KAAK,IAAI;AACvF,QAAM,OAAO,YAAY;AACzB,MAAI,CAAC,QAAQ,KAAK,SAAS,IAAK,OAAM,IAAI,MAAM,+CAA+C;AAC/F,SAAO;AACT;","names":[]}
@@ -0,0 +1,61 @@
1
+ // src/browser-auth/account-memberships.ts
2
+ import { createHash, randomUUID } from "node:crypto";
3
+ import { parseAccountMemberships } from "@alneas/eaos-server/platform";
4
+ function metadataHash(metadata) {
5
+ const canonical = (value) => Array.isArray(value) ? value.map(canonical) : value && typeof value === "object" ? Object.fromEntries(Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, item]) => [key, canonical(item)])) : value;
6
+ return createHash("sha256").update(JSON.stringify(canonical(metadata ?? {}))).digest("hex");
7
+ }
8
+ async function applyAccountMembershipChange(plan, actor, store, admin) {
9
+ if (!actor.trim()) throw Error("Operator identity required");
10
+ const field = plan.metadataField ?? "metadata_admin";
11
+ if (!["metadata_public", "metadata_admin"].includes(field)) throw Error("Invalid membership metadata field");
12
+ const target = parseAccountMemberships(plan.memberships);
13
+ const token = randomUUID();
14
+ await store.acquire(plan.identityId, token, actor);
15
+ const identity = await admin.read(plan.identityId);
16
+ if (identity.id !== plan.identityId || identity.state !== "active" || metadataHash(identity[field]) !== plan.beforeMetadataHash) throw Error("Stale identity migration plan; reconcile lock");
17
+ const next = { ...identity[field] ?? {}, eaos_memberships: target.memberships, eaos_default_tenant_id: target.defaultTenantId ?? null };
18
+ await admin.write(identity, next, field);
19
+ const verified = await admin.read(plan.identityId);
20
+ const afterHash = metadataHash(next);
21
+ if (verified.id !== identity.id || verified.state !== "active" || metadataHash(verified[field]) !== afterHash) throw Error("Identity update could not be verified; reconcile lock");
22
+ await store.complete(plan.identityId, token, actor, plan.beforeMetadataHash, afterHash);
23
+ }
24
+ var membershipKeys = ["eaos_memberships", "eaos_default_tenant_id"];
25
+ var identityMetadataHash = (identity) => metadataHash({ metadata_public: identity.metadata_public ?? {}, metadata_admin: identity.metadata_admin ?? {} });
26
+ function planAccountMembershipPublication(identity) {
27
+ if (identity.state !== "active") throw Error("Active identity required");
28
+ const source = identity.metadata_admin ?? {};
29
+ if (!Object.hasOwn(source, "eaos_memberships")) throw Error("Private memberships required for publication");
30
+ const memberships = parseAccountMemberships({ memberships: source.eaos_memberships, defaultTenantId: source.eaos_default_tenant_id ?? void 0 });
31
+ const existing = identity.metadata_public ?? {};
32
+ if (membershipKeys.some((key) => Object.hasOwn(existing, key))) throw Error("Public membership fields already exist; reconcile before publication");
33
+ return { identityId: identity.id, beforeMetadataHash: identityMetadataHash(identity), memberships };
34
+ }
35
+ async function publishAccountMemberships(plan, actor, store, admin) {
36
+ if (!actor.trim()) throw Error("Operator identity required");
37
+ const target = parseAccountMemberships(plan.memberships);
38
+ const token = randomUUID();
39
+ await store.acquire(plan.identityId, token, actor);
40
+ const identity = await admin.read(plan.identityId);
41
+ if (identity.id !== plan.identityId || identity.state !== "active" || identityMetadataHash(identity) !== plan.beforeMetadataHash) throw Error("Stale membership publication plan; reconcile lock");
42
+ const current = planAccountMembershipPublication(identity);
43
+ if (metadataHash(current.memberships) !== metadataHash(target)) throw Error("Publication plan changes memberships; reconcile lock");
44
+ const privateMetadata = { ...identity.metadata_admin ?? {} };
45
+ for (const key of membershipKeys) delete privateMetadata[key];
46
+ const publicMetadata = { ...identity.metadata_public ?? {}, eaos_memberships: target.memberships, eaos_default_tenant_id: target.defaultTenantId ?? null };
47
+ const next = { ...identity, metadata_admin: privateMetadata, metadata_public: publicMetadata };
48
+ await admin.write(next, publicMetadata, "metadata_public");
49
+ const verified = await admin.read(plan.identityId);
50
+ const afterHash = identityMetadataHash(next);
51
+ if (verified.id !== identity.id || verified.state !== "active" || identityMetadataHash(verified) !== afterHash) throw Error("Membership publication could not be verified; reconcile lock");
52
+ await store.complete(plan.identityId, token, actor, plan.beforeMetadataHash, afterHash);
53
+ }
54
+
55
+ export {
56
+ metadataHash,
57
+ applyAccountMembershipChange,
58
+ planAccountMembershipPublication,
59
+ publishAccountMemberships
60
+ };
61
+ //# sourceMappingURL=chunk-XMWPYZWZ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/browser-auth/account-memberships.ts"],"sourcesContent":["import { createHash, randomUUID } from 'node:crypto'\nimport { parseAccountMemberships, type AccountMemberships } from '@alneas/eaos-server/platform'\n/**\n * Provider metadata field updated by a membership migration; public metadata is visible to its account holder.\n * @see applyAccountMembershipChange.\n */\nexport type AccountMetadataField = 'metadata_public' | 'metadata_admin'\n/**\n * Provider identity snapshot used for optimistic checks and metadata-preserving writes.\n * @see planAccountMembershipPublication.\n */\nexport interface ManagedIdentity {\n /** Provider identity ID. */\n id: string\n /** Provider lifecycle state; membership operations require active. */\n state: string\n /** Optional editable traits; never used as membership authority here. */\n traits?: Record<string, unknown>\n /** Public provider metadata; published membership fields are user-visible. */\n metadata_public?: Record<string, unknown>\n /** Private administrator metadata; legacy membership migration defaults to this field. */\n metadata_admin?: Record<string, unknown>\n}\n/**\n * Reviewed membership replacement bound to a hash of the selected metadata snapshot.\n * @see applyAccountMembershipChange.\n */\nexport interface MembershipMigrationPlan {\n /** Identity whose metadata is being changed. */\n identityId: string\n /** metadataHash of the selected metadata field before the change. */\n beforeMetadataHash: string\n /** Validated target membership claims; all membership fields are replaced. */\n memberships: AccountMemberships\n /** Metadata destination; omitted preserves the metadata_admin migration contract. */\n metadataField?: AccountMetadataField\n}\n/**\n * Hashes metadata after recursively sorting object keys while preserving array order.\n * @param metadata - JSON-serializable metadata; null/undefined at the root are normalized to an empty object.\n * @returns Lower-case SHA-256 hexadecimal digest of canonicalized JSON.\n * @throws For cyclic, BigInt, or other values that JSON serialization cannot encode.\n * @remarks This is an optimistic snapshot comparison, not a signature or authentication mechanism.\n * @example\n * ```typescript\n * import { metadataHash } from \"@alneas/eaos-server/browser-auth/account-memberships\";\n * console.log(metadataHash({ b: 2, a: 1 }) === metadataHash({ a: 1, b: 2 })); // true\n * ```\n */\nexport function metadataHash(metadata: unknown): string {\n const canonical = (value: unknown): unknown => Array.isArray(value) ? value.map(canonical) : value && typeof value === 'object'\n ? Object.fromEntries(Object.entries(value).sort(([a],[b])=>a.localeCompare(b)).map(([key,item])=>[key,canonical(item)])) : value\n return createHash('sha256').update(JSON.stringify(canonical(metadata ?? {}))).digest('hex')\n}\n/**\n * Durable mutation lock and audit boundary for account metadata updates.\n * @remarks A failed or uncertain update retains its lock. The host must reconcile it rather than auto-expire or blindly retry.\n * @see applyAccountMembershipChange.\n */\nexport interface AccountMutationStore {\n /**\n * Acquires an exclusive durable mutation lock before provider reads/writes.\n * @param identityId - Provider identity being changed.\n * @param token - Fresh mutation token generated for this attempt.\n * @param actor - Nonblank authenticated operator identity.\n * @returns No value; resolves only when the lock is held.\n * @throws Rejects conflicting or unavailable persistence; provider work will not start.\n \n * @example\n * ```typescript\n * import type { AccountMutationStore } from \"@alneas/eaos-server/browser-auth\";\n * export async function lockReviewedIdentity(\n * store: AccountMutationStore, identityId: string, freshMutationToken: string, authenticatedActor: string,\n * ) {\n * await store.acquire(identityId, freshMutationToken, authenticatedActor);\n * // Keep the token until verified completion; uncertain writes require reconciliation.\n * }\n * ```\n */\n acquire(identityId: string, token: string, actor: string): Promise<void>\n /**\n * Records verified completion and finalizes the durable lock/audit state.\n * @param identityId - Changed provider identity.\n * @param token - Token acquired for this attempt; enforce ownership in the host store.\n * @param actor - Authenticated operator identity.\n * @param beforeHash - Reviewed pre-change snapshot hash.\n * @param afterHash - Hash read back and verified after the update.\n * @returns No value; resolves when completion is durably recorded.\n * @throws Persistence errors propagate after the provider may already have changed.\n \n * @example\n * ```typescript\n * import type { AccountMutationStore } from \"@alneas/eaos-server/browser-auth\";\n * export async function finishVerifiedIdentityMutation(\n * store: AccountMutationStore, identityId: string, ownedToken: string, authenticatedActor: string,\n * reviewedBeforeHash: string, verifiedAfterHash: string,\n * ) {\n * // Call only after reading back and verifying the actual provider metadata update.\n * await store.complete(identityId, ownedToken, authenticatedActor, reviewedBeforeHash, verifiedAfterHash);\n * }\n * ```\n */\n complete(identityId: string, token: string, actor: string, beforeHash: string, afterHash: string): Promise<void>\n}\n/**\n * Host-authorized provider administration used by account membership operations.\n * @see publishAccountMemberships.\n */\nexport interface AccountAdminPort {\n /**\n * Reads the authoritative current provider identity, including both metadata fields.\n * @param identityId - Provider identity ID.\n * @returns Current identity snapshot used for lifecycle and hash checks.\n * @throws Provider errors propagate and retain any acquired durable lock.\n \n * @example\n * ```typescript\n * import type { AccountAdminPort } from \"@alneas/eaos-server/browser-auth\";\n * export async function readAuthoritativeIdentity(admin: AccountAdminPort, identityId: string) {\n * // The host supplies an authorized provider-admin connection.\n * const identity = await admin.read(identityId);\n * return { id: identity.id, state: identity.state };\n * }\n * ```\n */\n read(identityId: string): Promise<ManagedIdentity>\n /**\n * Writes selected metadata while preserving unrelated identity fields.\n * @param identity - Authoritative identity snapshot; publication supplies both changed metadata fields and requires the port to PUT both in one provider request.\n * @param metadata - Complete replacement value for the selected metadata field.\n * @param field - Selected destination field.\n * @returns No value; resolves when the provider acknowledges the write, before independent readback verification.\n * @throws Provider failures propagate; the provider outcome may be uncertain and the durable lock must remain held.\n \n * @example\n * ```typescript\n * import type { AccountAdminPort } from \"@alneas/eaos-server/browser-auth\";\n * export async function writeReviewedIdentityMetadata(\n * admin: AccountAdminPort,\n * currentIdentity: Parameters<AccountAdminPort[\"write\"]>[0],\n * reviewedMetadata: Parameters<AccountAdminPort[\"write\"]>[1],\n * reviewedField: Parameters<AccountAdminPort[\"write\"]>[2],\n * ) {\n * // Host workflow acquires the mutation lock and checks the reviewed snapshot first.\n * await admin.write(currentIdentity, reviewedMetadata, reviewedField);\n * // Read back through admin.read before recording durable mutation completion.\n * }\n * ```\n */\n write(identity: ManagedIdentity, metadata: Record<string, unknown>, field: AccountMetadataField): Promise<void>\n}\n/**\n * Replaces membership fields under a durable lock and verifies provider readback before recording completion.\n * @param plan - Reviewed target claims, metadata field, and expected pre-change field hash.\n * @param actor - Nonblank authenticated operator identity; authorization is host-owned.\n * @param store - Durable exclusive lock and completion audit implementation.\n * @param admin - Trusted provider read/write implementation.\n * @returns No value; resolves after verified provider update and durable completion. Unrelated metadata keys are retained and absent defaultTenantId is written as null.\n * @throws Rejects invalid actor/metadata field/claims, stale or inactive identity, failed readback, or propagated lock/provider/completion errors.\n * @remarks A failed/uncertain mutation retains its durable lock. Reconcile; never auto-expire or blindly retry. This function does not roll back provider changes. Defaults to metadata_admin; explicitly selecting metadata_public exposes claims to the account holder.\n * @example\n * ```typescript\n * import { applyAccountMembershipChange, type MembershipMigrationPlan, type AccountMutationStore, type AccountAdminPort } from \"@alneas/eaos-server/browser-auth/account-memberships\";\n * export async function applyReviewedPlan(plan: MembershipMigrationPlan, actor: string, store: AccountMutationStore, admin: AccountAdminPort) {\n * await applyAccountMembershipChange(plan, actor, store, admin);\n * }\n * ```\n */\nexport async function applyAccountMembershipChange(plan: MembershipMigrationPlan, actor: string, store: AccountMutationStore, admin: AccountAdminPort) {\n if (!actor.trim()) throw Error('Operator identity required')\n // Preserve the existing private-metadata contract unless the host opts in.\n const field = plan.metadataField ?? 'metadata_admin'\n if (!['metadata_public', 'metadata_admin'].includes(field)) throw Error('Invalid membership metadata field')\n const target = parseAccountMemberships(plan.memberships)\n const token = randomUUID()\n await store.acquire(plan.identityId, token, actor)\n const identity = await admin.read(plan.identityId)\n if (identity.id !== plan.identityId || identity.state !== 'active' || metadataHash(identity[field]) !== plan.beforeMetadataHash) throw Error('Stale identity migration plan; reconcile lock')\n const next = { ...(identity[field] ?? {}), eaos_memberships: target.memberships, eaos_default_tenant_id: target.defaultTenantId ?? null }\n // Null clears a previous default; reader treats it as absent.\n await admin.write(identity, next, field)\n const verified = await admin.read(plan.identityId)\n const afterHash = metadataHash(next)\n if (verified.id !== identity.id || verified.state !== 'active' || metadataHash(verified[field]) !== afterHash) throw Error('Identity update could not be verified; reconcile lock')\n await store.complete(plan.identityId, token, actor, plan.beforeMetadataHash, afterHash)\n}\n\n/**\n * Reviewed plan moving existing private membership fields into public metadata without changing claims.\n * @see planAccountMembershipPublication.\n */\nexport interface MembershipPublicationPlan {\n /** Active identity whose memberships will become public. */\n identityId: string\n /** Combined public/admin metadata snapshot hash used to detect stale plans. */\n beforeMetadataHash: string\n /** Validated claims copied from private membership fields. */\n memberships: AccountMemberships\n}\nconst membershipKeys = ['eaos_memberships', 'eaos_default_tenant_id'] as const\nconst identityMetadataHash = (identity: ManagedIdentity) => metadataHash({ metadata_public: identity.metadata_public ?? {}, metadata_admin: identity.metadata_admin ?? {} })\n\n/**\n * Plans moving existing administrator membership claims into public identity metadata.\n * @param identity - Active authoritative identity snapshot containing private membership fields and no public membership fields.\n * @returns A new publication plan binding parsed claims to the combined public/admin metadata hash; no writes occur.\n * @throws For inactive identity, missing private memberships, existing public membership fields, invalid claims, or unhashable metadata.\n * @remarks Review the returned plan before publication: membership fields become visible to the account holder.\n * @example\n * ```typescript\n * import { planAccountMembershipPublication, type AccountAdminPort } from \"@alneas/eaos-server/browser-auth/account-memberships\";\n * export async function reviewPlan(admin: AccountAdminPort, identityId: string) {\n * return planAccountMembershipPublication(await admin.read(identityId));\n * }\n * ```\n */\nexport function planAccountMembershipPublication(identity: ManagedIdentity): MembershipPublicationPlan {\n if (identity.state !== 'active') throw Error('Active identity required')\n const source = identity.metadata_admin ?? {}\n if (!Object.hasOwn(source, 'eaos_memberships')) throw Error('Private memberships required for publication')\n const memberships = parseAccountMemberships({ memberships: source.eaos_memberships, defaultTenantId: source.eaos_default_tenant_id ?? undefined })\n const existing = identity.metadata_public ?? {}\n if (membershipKeys.some(key => Object.hasOwn(existing, key))) throw Error('Public membership fields already exist; reconcile before publication')\n return { identityId: identity.id, beforeMetadataHash: identityMetadataHash(identity), memberships }\n}\n\n/**\n * Moves reviewed membership fields from administrator to public metadata in one provider write and verifies readback.\n * @param plan - Reviewed publication plan; current private claims must still match exactly.\n * @param actor - Nonblank authenticated operator identity.\n * @param store - Durable exclusive mutation lock and completion audit.\n * @param admin - Provider port that must write both metadata fields from the supplied identity in one request.\n * @returns No value; resolves after verified readback and durable completion, retaining unrelated public/private metadata.\n * @throws Rejects invalid actor/claims, stale snapshot, changed memberships, failed provider verification, or propagated store/provider failures.\n * @remarks The durable lock remains on any failed or uncertain outcome; reconcile instead of blind retry. This function does not roll back or independently authenticate the operator. Atomicity of the two-field update depends on the host port honoring its single-request contract.\n * @example\n * ```typescript\n * import { publishAccountMemberships, type MembershipPublicationPlan, type AccountMutationStore, type AccountAdminPort } from \"@alneas/eaos-server/browser-auth/account-memberships\";\n * export async function publishReviewedPlan(plan: MembershipPublicationPlan, actor: string, store: AccountMutationStore, admin: AccountAdminPort) {\n * await publishAccountMemberships(plan, actor, store, admin);\n * }\n * ```\n */\nexport async function publishAccountMemberships(plan: MembershipPublicationPlan, actor: string, store: AccountMutationStore, admin: AccountAdminPort) {\n if (!actor.trim()) throw Error('Operator identity required')\n const target = parseAccountMemberships(plan.memberships)\n const token = randomUUID()\n await store.acquire(plan.identityId, token, actor)\n const identity = await admin.read(plan.identityId)\n if (identity.id !== plan.identityId || identity.state !== 'active' || identityMetadataHash(identity) !== plan.beforeMetadataHash) throw Error('Stale membership publication plan; reconcile lock')\n const current = planAccountMembershipPublication(identity)\n if (metadataHash(current.memberships) !== metadataHash(target)) throw Error('Publication plan changes memberships; reconcile lock')\n const privateMetadata = { ...(identity.metadata_admin ?? {}) }\n for (const key of membershipKeys) delete privateMetadata[key]\n const publicMetadata = { ...(identity.metadata_public ?? {}), eaos_memberships: target.memberships, eaos_default_tenant_id: target.defaultTenantId ?? null }\n const next = { ...identity, metadata_admin: privateMetadata, metadata_public: publicMetadata }\n // The port must PUT both metadata fields from this identity in one request.\n await admin.write(next, publicMetadata, 'metadata_public')\n const verified = await admin.read(plan.identityId)\n const afterHash = identityMetadataHash(next)\n if (verified.id !== identity.id || verified.state !== 'active' || identityMetadataHash(verified) !== afterHash) throw Error('Membership publication could not be verified; reconcile lock')\n await store.complete(plan.identityId, token, actor, plan.beforeMetadataHash, afterHash)\n}\n"],"mappings":"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","names":[]}