@segmentstream/ontology 0.0.0-stage → 0.92.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 (483) hide show
  1. package/LICENSE +201 -0
  2. package/NOTICE +2 -0
  3. package/README.md +1655 -2
  4. package/dist/action.d.ts +193 -0
  5. package/dist/action.js +41 -0
  6. package/dist/agent-chat.d.ts +44 -0
  7. package/dist/agent-chat.js +91 -0
  8. package/dist/agent-task-contract.d.ts +64 -0
  9. package/dist/agent-task-contract.js +15 -0
  10. package/dist/agent-task.d.ts +39 -0
  11. package/dist/agent-task.js +112 -0
  12. package/dist/analyze.d.ts +215 -0
  13. package/dist/analyze.js +2438 -0
  14. package/dist/app-feedback.d.ts +167 -0
  15. package/dist/app-feedback.js +176 -0
  16. package/dist/authoring-input.d.ts +3 -0
  17. package/dist/authoring-input.js +107 -0
  18. package/dist/authoring-names.d.ts +4 -0
  19. package/dist/authoring-names.js +13 -0
  20. package/dist/authoring-records.d.ts +35 -0
  21. package/dist/authoring-records.js +264 -0
  22. package/dist/authoring.d.ts +15 -0
  23. package/dist/authoring.js +331 -0
  24. package/dist/bin/check.d.ts +30 -0
  25. package/dist/bin/check.js +99 -0
  26. package/dist/bin/load.d.ts +48 -0
  27. package/dist/bin/load.js +139 -0
  28. package/dist/bin/main.d.ts +31 -0
  29. package/dist/bin/main.js +121 -0
  30. package/dist/bin/migrate.d.ts +57 -0
  31. package/dist/bin/migrate.js +170 -0
  32. package/dist/bin/output.d.ts +31 -0
  33. package/dist/bin/output.js +84 -0
  34. package/dist/bin.d.ts +2 -0
  35. package/dist/bin.js +31 -0
  36. package/dist/blob.d.ts +52 -0
  37. package/dist/blob.js +87 -0
  38. package/dist/channel-session.d.ts +16 -0
  39. package/dist/channel-session.js +16 -0
  40. package/dist/chat.d.ts +43 -0
  41. package/dist/chat.js +34 -0
  42. package/dist/confirmation.d.ts +46 -0
  43. package/dist/confirmation.js +41 -0
  44. package/dist/connection.d.ts +213 -0
  45. package/dist/connection.js +289 -0
  46. package/dist/deployment.d.ts +35 -0
  47. package/dist/deployment.js +76 -0
  48. package/dist/diagnostics.d.ts +27 -0
  49. package/dist/diagnostics.js +106 -0
  50. package/dist/emails.d.ts +70 -0
  51. package/dist/emails.js +221 -0
  52. package/dist/entity.d.ts +331 -0
  53. package/dist/entity.js +153 -0
  54. package/dist/errors.d.ts +154 -0
  55. package/dist/errors.js +109 -0
  56. package/dist/feedback-config.d.ts +24 -0
  57. package/dist/feedback-config.js +126 -0
  58. package/dist/files.d.ts +39 -0
  59. package/dist/files.js +4 -0
  60. package/dist/groups.d.ts +95 -0
  61. package/dist/groups.js +280 -0
  62. package/dist/index.d.ts +109 -0
  63. package/dist/index.js +70 -0
  64. package/dist/internal/diagnostics.js +594 -0
  65. package/dist/legacy.d.ts +160 -0
  66. package/dist/legacy.js +270 -0
  67. package/dist/manifest-compatibility.d.ts +8 -0
  68. package/dist/manifest-compatibility.js +87 -0
  69. package/dist/manifest.d.ts +3329 -0
  70. package/dist/manifest.js +757 -0
  71. package/dist/me.d.ts +46 -0
  72. package/dist/me.js +109 -0
  73. package/dist/metric-filters.d.ts +48 -0
  74. package/dist/metric-filters.js +252 -0
  75. package/dist/metric-routes.d.ts +132 -0
  76. package/dist/metric-routes.js +498 -0
  77. package/dist/metric.d.ts +172 -0
  78. package/dist/metric.js +132 -0
  79. package/dist/migrate/apply.d.ts +94 -0
  80. package/dist/migrate/apply.js +526 -0
  81. package/dist/migrate/errors.d.ts +46 -0
  82. package/dist/migrate/errors.js +38 -0
  83. package/dist/migrate/index.d.ts +10 -0
  84. package/dist/migrate/index.js +7 -0
  85. package/dist/migrate/model.d.ts +2311 -0
  86. package/dist/migrate/model.js +148 -0
  87. package/dist/migrate/plan.d.ts +51 -0
  88. package/dist/migrate/plan.js +1856 -0
  89. package/dist/migrate/preview.d.ts +13 -0
  90. package/dist/migrate/preview.js +65 -0
  91. package/dist/migrate/storage.d.ts +226 -0
  92. package/dist/migrate/storage.js +774 -0
  93. package/dist/model-metrics.d.ts +22 -0
  94. package/dist/model-metrics.js +545 -0
  95. package/dist/names.d.ts +49 -0
  96. package/dist/names.js +110 -0
  97. package/dist/navigation-artifact.d.ts +13 -0
  98. package/dist/navigation-artifact.js +11 -0
  99. package/dist/navigation-catalog.d.ts +55 -0
  100. package/dist/navigation-catalog.js +297 -0
  101. package/dist/nitro-navigation.d.ts +3 -0
  102. package/dist/nitro-navigation.js +64 -0
  103. package/dist/nitro-workflows.d.ts +108 -0
  104. package/dist/nitro-workflows.js +286 -0
  105. package/dist/nitro.d.ts +40 -0
  106. package/dist/nitro.js +50 -0
  107. package/dist/ontology.d.ts +41 -0
  108. package/dist/ontology.js +46 -0
  109. package/dist/permissions.d.ts +198 -0
  110. package/dist/permissions.js +272 -0
  111. package/dist/property.d.ts +188 -0
  112. package/dist/property.js +301 -0
  113. package/dist/query.d.ts +245 -0
  114. package/dist/query.js +615 -0
  115. package/dist/relationship.d.ts +99 -0
  116. package/dist/relationship.js +33 -0
  117. package/dist/render.d.ts +81 -0
  118. package/dist/render.js +512 -0
  119. package/dist/replication/baseline.d.ts +51 -0
  120. package/dist/replication/baseline.js +215 -0
  121. package/dist/replication/bigquery.d.ts +37 -0
  122. package/dist/replication/bigquery.js +211 -0
  123. package/dist/replication/coverage.d.ts +14 -0
  124. package/dist/replication/coverage.js +53 -0
  125. package/dist/replication/evolution.d.ts +25 -0
  126. package/dist/replication/evolution.js +184 -0
  127. package/dist/replication/identities.d.ts +6 -0
  128. package/dist/replication/identities.js +47 -0
  129. package/dist/replication/legacy-baseline.d.ts +27 -0
  130. package/dist/replication/legacy-baseline.js +109 -0
  131. package/dist/replication/legacy-store.d.ts +19 -0
  132. package/dist/replication/legacy-store.js +63 -0
  133. package/dist/replication/lineage.d.ts +36 -0
  134. package/dist/replication/lineage.js +72 -0
  135. package/dist/replication/migrate.d.ts +21 -0
  136. package/dist/replication/migrate.js +84 -0
  137. package/dist/replication/model.d.ts +52 -0
  138. package/dist/replication/model.js +99 -0
  139. package/dist/replication/scans.d.ts +34 -0
  140. package/dist/replication/scans.js +158 -0
  141. package/dist/replication/schema.d.ts +11 -0
  142. package/dist/replication/schema.js +226 -0
  143. package/dist/replication/shadow-schema.d.ts +4 -0
  144. package/dist/replication/shadow-schema.js +61 -0
  145. package/dist/replication/shadow.d.ts +127 -0
  146. package/dist/replication/shadow.js +614 -0
  147. package/dist/replication/source-replacement.d.ts +33 -0
  148. package/dist/replication/source-replacement.js +76 -0
  149. package/dist/replication/store.d.ts +36 -0
  150. package/dist/replication/store.js +382 -0
  151. package/dist/replication/transaction.d.ts +23 -0
  152. package/dist/replication/transaction.js +88 -0
  153. package/dist/role.d.ts +61 -0
  154. package/dist/role.js +76 -0
  155. package/dist/runtime/action-deadlocks.d.ts +21 -0
  156. package/dist/runtime/action-deadlocks.js +130 -0
  157. package/dist/runtime/action-transactions.d.ts +35 -0
  158. package/dist/runtime/action-transactions.js +54 -0
  159. package/dist/runtime/agent-counters.d.ts +26 -0
  160. package/dist/runtime/agent-counters.js +98 -0
  161. package/dist/runtime/agent-tasks.d.ts +63 -0
  162. package/dist/runtime/agent-tasks.js +120 -0
  163. package/dist/runtime/agents.d.ts +15 -0
  164. package/dist/runtime/agents.js +457 -0
  165. package/dist/runtime/batch-diagnostics.d.ts +22 -0
  166. package/dist/runtime/batch-diagnostics.js +89 -0
  167. package/dist/runtime/batch.d.ts +52 -0
  168. package/dist/runtime/batch.js +534 -0
  169. package/dist/runtime/channel-session-revocation.d.ts +3 -0
  170. package/dist/runtime/channel-session-revocation.js +5 -0
  171. package/dist/runtime/channel-sessions.d.ts +4 -0
  172. package/dist/runtime/channel-sessions.js +61 -0
  173. package/dist/runtime/confirmations.d.ts +29 -0
  174. package/dist/runtime/confirmations.js +57 -0
  175. package/dist/runtime/cursor.d.ts +23 -0
  176. package/dist/runtime/cursor.js +100 -0
  177. package/dist/runtime/deployment.d.ts +7 -0
  178. package/dist/runtime/deployment.js +4 -0
  179. package/dist/runtime/errors.d.ts +118 -0
  180. package/dist/runtime/errors.js +109 -0
  181. package/dist/runtime/federation-tables.d.ts +2 -0
  182. package/dist/runtime/federation-tables.js +20 -0
  183. package/dist/runtime/federation.d.ts +42 -0
  184. package/dist/runtime/federation.js +222 -0
  185. package/dist/runtime/files.d.ts +39 -0
  186. package/dist/runtime/files.js +44 -0
  187. package/dist/runtime/generated-actions.d.ts +37 -0
  188. package/dist/runtime/generated-actions.js +307 -0
  189. package/dist/runtime/history.d.ts +91 -0
  190. package/dist/runtime/history.js +77 -0
  191. package/dist/runtime/ids.d.ts +6 -0
  192. package/dist/runtime/ids.js +39 -0
  193. package/dist/runtime/member-tables.d.ts +8 -0
  194. package/dist/runtime/member-tables.js +33 -0
  195. package/dist/runtime/member-writes.d.ts +78 -0
  196. package/dist/runtime/member-writes.js +202 -0
  197. package/dist/runtime/metric-query.d.ts +19 -0
  198. package/dist/runtime/metric-query.js +411 -0
  199. package/dist/runtime/model.d.ts +67 -0
  200. package/dist/runtime/model.js +150 -0
  201. package/dist/runtime/new-user-hook.d.ts +6 -0
  202. package/dist/runtime/new-user-hook.js +37 -0
  203. package/dist/runtime/oauth.d.ts +9 -0
  204. package/dist/runtime/oauth.js +299 -0
  205. package/dist/runtime/pipeline.d.ts +155 -0
  206. package/dist/runtime/pipeline.js +597 -0
  207. package/dist/runtime/postgres-tls.d.ts +10 -0
  208. package/dist/runtime/postgres-tls.js +14 -0
  209. package/dist/runtime/principals.d.ts +302 -0
  210. package/dist/runtime/principals.js +1307 -0
  211. package/dist/runtime/reads.d.ts +55 -0
  212. package/dist/runtime/reads.js +313 -0
  213. package/dist/runtime/records.d.ts +42 -0
  214. package/dist/runtime/records.js +245 -0
  215. package/dist/runtime/resolve-agent-identity.d.ts +6 -0
  216. package/dist/runtime/resolve-agent-identity.js +181 -0
  217. package/dist/runtime/service-binding.d.ts +18 -0
  218. package/dist/runtime/service-binding.js +266 -0
  219. package/dist/runtime/sign-in-links.d.ts +68 -0
  220. package/dist/runtime/sign-in-links.js +147 -0
  221. package/dist/runtime/sql-runtime.d.ts +131 -0
  222. package/dist/runtime/sql-runtime.js +362 -0
  223. package/dist/runtime/statement.d.ts +18 -0
  224. package/dist/runtime/statement.js +31 -0
  225. package/dist/runtime/user-identities.d.ts +66 -0
  226. package/dist/runtime/user-identities.js +106 -0
  227. package/dist/runtime/user-identity-sql.d.ts +86 -0
  228. package/dist/runtime/user-identity-sql.js +297 -0
  229. package/dist/runtime/user-roles.d.ts +16 -0
  230. package/dist/runtime/user-roles.js +32 -0
  231. package/dist/runtime/validation.d.ts +16 -0
  232. package/dist/runtime/validation.js +26 -0
  233. package/dist/runtime/where.d.ts +57 -0
  234. package/dist/runtime/where.js +404 -0
  235. package/dist/runtime/workflow-admission.d.ts +49 -0
  236. package/dist/runtime/workflow-admission.js +92 -0
  237. package/dist/runtime/workflow-argument-codec.d.ts +10 -0
  238. package/dist/runtime/workflow-argument-codec.js +120 -0
  239. package/dist/runtime/workflow-arguments.d.ts +17 -0
  240. package/dist/runtime/workflow-arguments.js +99 -0
  241. package/dist/runtime/workflow-starts.d.ts +203 -0
  242. package/dist/runtime/workflow-starts.js +493 -0
  243. package/dist/runtime/workflow-users.d.ts +9 -0
  244. package/dist/runtime/workflow-users.js +17 -0
  245. package/dist/runtime/workload.d.ts +25 -0
  246. package/dist/runtime/workload.js +24 -0
  247. package/dist/runtime/writes.d.ts +108 -0
  248. package/dist/runtime/writes.js +491 -0
  249. package/dist/runtime-source.d.ts +22 -0
  250. package/dist/runtime-source.js +28 -0
  251. package/dist/runtime.d.ts +310 -0
  252. package/dist/runtime.js +31 -0
  253. package/dist/server.d.ts +23 -0
  254. package/dist/server.js +107 -0
  255. package/dist/service/agent/attachments.d.ts +33 -0
  256. package/dist/service/agent/attachments.js +228 -0
  257. package/dist/service/agent/authentication.d.ts +22 -0
  258. package/dist/service/agent/authentication.js +58 -0
  259. package/dist/service/agent/confirmations.d.ts +5 -0
  260. package/dist/service/agent/confirmations.js +73 -0
  261. package/dist/service/agent/conversation-routes.d.ts +14 -0
  262. package/dist/service/agent/conversation-routes.js +45 -0
  263. package/dist/service/agent/conversations.d.ts +40 -0
  264. package/dist/service/agent/conversations.js +69 -0
  265. package/dist/service/agent/files.d.ts +12 -0
  266. package/dist/service/agent/files.js +69 -0
  267. package/dist/service/agent/protocol.d.ts +90 -0
  268. package/dist/service/agent/protocol.js +114 -0
  269. package/dist/service/agent/server.d.ts +28 -0
  270. package/dist/service/agent/server.js +374 -0
  271. package/dist/service/agent/task-caller.d.ts +21 -0
  272. package/dist/service/agent/task-caller.js +35 -0
  273. package/dist/service/agent/task-start.d.ts +53 -0
  274. package/dist/service/agent/task-start.js +161 -0
  275. package/dist/service/agent/tasks.d.ts +40 -0
  276. package/dist/service/agent/tasks.js +155 -0
  277. package/dist/service/api.d.ts +50 -0
  278. package/dist/service/api.js +288 -0
  279. package/dist/service/authentication.d.ts +227 -0
  280. package/dist/service/authentication.js +767 -0
  281. package/dist/service/caller-context.d.ts +24 -0
  282. package/dist/service/caller-context.js +6 -0
  283. package/dist/service/catalog.d.ts +42 -0
  284. package/dist/service/catalog.js +66 -0
  285. package/dist/service/channel/provider.d.ts +10 -0
  286. package/dist/service/channel/provider.js +1 -0
  287. package/dist/service/channel/providers/telegram.d.ts +18 -0
  288. package/dist/service/channel/providers/telegram.js +108 -0
  289. package/dist/service/channel/sessions.d.ts +5 -0
  290. package/dist/service/channel/sessions.js +53 -0
  291. package/dist/service/channel/sign-in.d.ts +7 -0
  292. package/dist/service/channel/sign-in.js +78 -0
  293. package/dist/service/chat/archive.d.ts +64 -0
  294. package/dist/service/chat/archive.js +62 -0
  295. package/dist/service/chat/attribution.d.ts +26 -0
  296. package/dist/service/chat/attribution.js +28 -0
  297. package/dist/service/chat/delivery.d.ts +18 -0
  298. package/dist/service/chat/delivery.js +251 -0
  299. package/dist/service/chat/diagnostics.d.ts +2 -0
  300. package/dist/service/chat/diagnostics.js +35 -0
  301. package/dist/service/chat/envelope.d.ts +96 -0
  302. package/dist/service/chat/envelope.js +39 -0
  303. package/dist/service/chat/idempotency.d.ts +19 -0
  304. package/dist/service/chat/idempotency.js +37 -0
  305. package/dist/service/chat/providers/telegram.d.ts +36 -0
  306. package/dist/service/chat/providers/telegram.js +133 -0
  307. package/dist/service/chat/reads.d.ts +25 -0
  308. package/dist/service/chat/reads.js +75 -0
  309. package/dist/service/chat/resolve.d.ts +34 -0
  310. package/dist/service/chat/resolve.js +150 -0
  311. package/dist/service/chat/routes.d.ts +23 -0
  312. package/dist/service/chat/routes.js +75 -0
  313. package/dist/service/chat/schemas.d.ts +43 -0
  314. package/dist/service/chat/schemas.js +31 -0
  315. package/dist/service/config.d.ts +218 -0
  316. package/dist/service/config.js +265 -0
  317. package/dist/service/connection-stream.d.ts +10 -0
  318. package/dist/service/connection-stream.js +191 -0
  319. package/dist/service/connections.d.ts +60 -0
  320. package/dist/service/connections.js +271 -0
  321. package/dist/service/decisions.d.ts +35 -0
  322. package/dist/service/decisions.js +63 -0
  323. package/dist/service/descriptions.d.ts +8 -0
  324. package/dist/service/descriptions.js +20 -0
  325. package/dist/service/discovery-schemas.d.ts +92 -0
  326. package/dist/service/discovery-schemas.js +59 -0
  327. package/dist/service/discovery-search.d.ts +30 -0
  328. package/dist/service/discovery-search.js +95 -0
  329. package/dist/service/discovery.d.ts +37 -0
  330. package/dist/service/discovery.js +436 -0
  331. package/dist/service/docs.d.ts +9 -0
  332. package/dist/service/docs.js +197 -0
  333. package/dist/service/errors.d.ts +145 -0
  334. package/dist/service/errors.js +704 -0
  335. package/dist/service/federation-provider.d.ts +36 -0
  336. package/dist/service/federation-provider.js +110 -0
  337. package/dist/service/federation-server.d.ts +20 -0
  338. package/dist/service/federation-server.js +249 -0
  339. package/dist/service/federation-status.d.ts +40 -0
  340. package/dist/service/federation-status.js +151 -0
  341. package/dist/service/handlers.d.ts +18 -0
  342. package/dist/service/handlers.js +287 -0
  343. package/dist/service/mcp/confirmation.d.ts +68 -0
  344. package/dist/service/mcp/confirmation.js +178 -0
  345. package/dist/service/mcp/find-input.d.ts +12 -0
  346. package/dist/service/mcp/find-input.js +122 -0
  347. package/dist/service/mcp/input-schema.d.ts +6 -0
  348. package/dist/service/mcp/input-schema.js +126 -0
  349. package/dist/service/mcp/listing.d.ts +26 -0
  350. package/dist/service/mcp/listing.js +92 -0
  351. package/dist/service/mcp/metric-tools.d.ts +85 -0
  352. package/dist/service/mcp/metric-tools.js +258 -0
  353. package/dist/service/mcp/request-log.d.ts +5 -0
  354. package/dist/service/mcp/request-log.js +53 -0
  355. package/dist/service/mcp/runtime-policy.d.ts +27 -0
  356. package/dist/service/mcp/runtime-policy.js +62 -0
  357. package/dist/service/mcp/server.d.ts +52 -0
  358. package/dist/service/mcp/server.js +861 -0
  359. package/dist/service/mcp/tools.d.ts +99 -0
  360. package/dist/service/mcp/tools.js +938 -0
  361. package/dist/service/member-file-routes.d.ts +22 -0
  362. package/dist/service/member-file-routes.js +52 -0
  363. package/dist/service/member-files.d.ts +245 -0
  364. package/dist/service/member-files.js +258 -0
  365. package/dist/service/metric-query-platform.d.ts +77 -0
  366. package/dist/service/metric-query-platform.js +198 -0
  367. package/dist/service/metric-query-scopes.d.ts +43 -0
  368. package/dist/service/metric-query-scopes.js +147 -0
  369. package/dist/service/metric-query.d.ts +48 -0
  370. package/dist/service/metric-query.js +200 -0
  371. package/dist/service/name-suggestions.d.ts +3 -0
  372. package/dist/service/name-suggestions.js +38 -0
  373. package/dist/service/navigation-schemas.d.ts +80 -0
  374. package/dist/service/navigation-schemas.js +49 -0
  375. package/dist/service/navigation.d.ts +20 -0
  376. package/dist/service/navigation.js +165 -0
  377. package/dist/service/oauth/protocol.d.ts +70 -0
  378. package/dist/service/oauth/protocol.js +157 -0
  379. package/dist/service/oauth/refresh-replay.d.ts +2 -0
  380. package/dist/service/oauth/refresh-replay.js +20 -0
  381. package/dist/service/oauth/server.d.ts +28 -0
  382. package/dist/service/oauth/server.js +769 -0
  383. package/dist/service/operations.d.ts +170 -0
  384. package/dist/service/operations.js +392 -0
  385. package/dist/service/plan.d.ts +57 -0
  386. package/dist/service/plan.js +96 -0
  387. package/dist/service/rate-limit.d.ts +22 -0
  388. package/dist/service/rate-limit.js +52 -0
  389. package/dist/service/record-reads.d.ts +21 -0
  390. package/dist/service/record-reads.js +155 -0
  391. package/dist/service/replication.d.ts +5 -0
  392. package/dist/service/replication.js +39 -0
  393. package/dist/service/runtime-contract.d.ts +508 -0
  394. package/dist/service/runtime-contract.js +18 -0
  395. package/dist/service/runtime-identity.d.ts +71 -0
  396. package/dist/service/runtime-identity.js +122 -0
  397. package/dist/service/schemas.d.ts +961 -0
  398. package/dist/service/schemas.js +639 -0
  399. package/dist/service/service.d.ts +21 -0
  400. package/dist/service/service.js +526 -0
  401. package/dist/service/warehouse-platform.d.ts +17 -0
  402. package/dist/service/warehouse-platform.js +122 -0
  403. package/dist/service/warehouse-query.d.ts +24 -0
  404. package/dist/service/warehouse-query.js +280 -0
  405. package/dist/service/webhooks.d.ts +106 -0
  406. package/dist/service/webhooks.js +233 -0
  407. package/dist/service/workflow-admission-provider.d.ts +31 -0
  408. package/dist/service/workflow-admission-provider.js +205 -0
  409. package/dist/service/workflow-argument-serialization.d.ts +2 -0
  410. package/dist/service/workflow-argument-serialization.js +14 -0
  411. package/dist/service/workflow-callback.d.ts +22 -0
  412. package/dist/service/workflow-callback.js +75 -0
  413. package/dist/service/workflow-deployment.d.ts +8 -0
  414. package/dist/service/workflow-deployment.js +131 -0
  415. package/dist/service/workflow-diagnostics.d.ts +23 -0
  416. package/dist/service/workflow-diagnostics.js +263 -0
  417. package/dist/service/workflow-errors.d.ts +26 -0
  418. package/dist/service/workflow-errors.js +181 -0
  419. package/dist/service/workflow-logging.d.ts +2 -0
  420. package/dist/service/workflow-logging.js +50 -0
  421. package/dist/service/workflow-queue-fence.d.ts +6 -0
  422. package/dist/service/workflow-queue-fence.js +66 -0
  423. package/dist/service/workflows.d.ts +200 -0
  424. package/dist/service/workflows.js +1549 -0
  425. package/dist/sign-in.d.ts +58 -0
  426. package/dist/sign-in.js +57 -0
  427. package/dist/source.d.ts +30 -0
  428. package/dist/source.js +107 -0
  429. package/dist/start-recovery-contract.d.ts +49 -0
  430. package/dist/start-recovery-contract.js +26 -0
  431. package/dist/start-recovery.d.ts +27 -0
  432. package/dist/start-recovery.js +81 -0
  433. package/dist/storage/api.d.ts +4 -0
  434. package/dist/storage/api.js +40 -0
  435. package/dist/storage/attachments.d.ts +146 -0
  436. package/dist/storage/attachments.js +733 -0
  437. package/dist/storage/authorization.d.ts +38 -0
  438. package/dist/storage/authorization.js +83 -0
  439. package/dist/storage/file-access.d.ts +17 -0
  440. package/dist/storage/file-access.js +8 -0
  441. package/dist/storage/limits.d.ts +12 -0
  442. package/dist/storage/limits.js +17 -0
  443. package/dist/storage/maintenance-auth.d.ts +20 -0
  444. package/dist/storage/maintenance-auth.js +24 -0
  445. package/dist/storage/maintenance.d.ts +11 -0
  446. package/dist/storage/maintenance.js +67 -0
  447. package/dist/storage/neon.d.ts +4 -0
  448. package/dist/storage/neon.js +318 -0
  449. package/dist/storage/operator.d.ts +11 -0
  450. package/dist/storage/operator.js +30 -0
  451. package/dist/storage/provider.d.ts +93 -0
  452. package/dist/storage/provider.js +36 -0
  453. package/dist/storage/sessions.d.ts +184 -0
  454. package/dist/storage/sessions.js +688 -0
  455. package/dist/storage/tables.d.ts +54 -0
  456. package/dist/storage/tables.js +205 -0
  457. package/dist/storage/transfers.d.ts +16 -0
  458. package/dist/storage/transfers.js +153 -0
  459. package/dist/storage/writes.d.ts +7 -0
  460. package/dist/storage/writes.js +88 -0
  461. package/dist/telegram.d.ts +35 -0
  462. package/dist/telegram.js +173 -0
  463. package/dist/user-identity.d.ts +28 -0
  464. package/dist/user-identity.js +106 -0
  465. package/dist/user.d.ts +75 -0
  466. package/dist/user.js +111 -0
  467. package/dist/warehouse-backed.d.ts +6 -0
  468. package/dist/warehouse-backed.js +495 -0
  469. package/dist/webhook-token.d.ts +11 -0
  470. package/dist/webhook-token.js +18 -0
  471. package/dist/webhooks.d.ts +40 -0
  472. package/dist/webhooks.js +55 -0
  473. package/dist/wire.d.ts +10 -0
  474. package/dist/wire.js +35 -0
  475. package/dist/workflow-runtime.d.ts +116 -0
  476. package/dist/workflow-runtime.js +109 -0
  477. package/dist/workflow-topics.d.ts +28 -0
  478. package/dist/workflow-topics.js +28 -0
  479. package/dist/workflow.d.ts +284 -0
  480. package/dist/workflow.js +259 -0
  481. package/dist/workflows.d.ts +24 -0
  482. package/dist/workflows.js +18 -0
  483. package/package.json +127 -4
package/README.md CHANGED
@@ -1,3 +1,1656 @@
1
- # Temporary Holding Version
1
+ # @segmentstream/ontology
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Releases up to the scope change were published as `@segmentstream-cloud/ontology`; versions continue
4
+ from them, with the same exports and API.
5
+
6
+ The backend of a SegmentStream workspace app, written with [Effect](https://effect.website).
7
+ The `ontology/` folder of the workspace repository declares entities, their properties,
8
+ the relationships between them, the roles and the actions people and agents may take.
9
+ From that one definition, the SDK builds the manifest, the tables in the workspace's
10
+ database and the HTTP service under `/api/ontology`. Every read and every action requires
11
+ a permission that the caller's roles grant, and every change to a record goes through an
12
+ action and is kept in history.
13
+
14
+ TypeScript definitions, handler records and SDK calls use camelCase: `jobTitle`,
15
+ `assignedOwnerId`, `createdAt`, and `changeStatus`. The SDK converts ontology names,
16
+ select identifiers and declared action input fields to snake_case for the generated
17
+ REST API, version 10 manifest and storage. TypeScript helpers and options stay
18
+ camelCase. JSON property contents, provider payloads, opaque action results and
19
+ historical change names remain data and are preserved. `formatManifest` serializes
20
+ the canonical wire contract shared by REST, storage and future transports.
21
+
22
+ When calling REST or MCP directly, use the snake_case wire name:
23
+
24
+ | TypeScript action | REST path | MCP tool |
25
+ | ------------------ | --------------------------------------------------------- | ----------------------------------- |
26
+ | `createDraft` | `/api/ontology/expense/actions/create_draft` | `expense_create_draft` |
27
+ | `attributePayroll` | `/api/ontology/bank_allocation/actions/attribute_payroll` | `bank_allocation_attribute_payroll` |
28
+
29
+ `ontology check` lists canonical wire action names. MCP `tools/list` gives exact tool names.
30
+ Incorrect casing and close typos
31
+ produce hints with a `suggestions` array for authenticated callers who may run the suggested
32
+ actions (MCP unknown-tool errors put it in `error.data`). Hints never execute an action or create
33
+ an alias; retry explicitly with the suggested name.
34
+
35
+ ## Changes
36
+
37
+ Workspace deployment is automatic on main pushes by default. To accumulate changes for an
38
+ explicit release, commit `.segmentstream/deployment.json` with
39
+ `{ "schemaVersion": 1, "trigger": "manual" }`. Cloud keeps storing and indexing source,
40
+ while `segmentstream deploy` explicitly deploys current main with its existing authorization
41
+ and idempotency checks. The configuration takes effect from current main without an ontology
42
+ migration. Removing the file or selecting `"push"` restores automatic deployment; no scheduler
43
+ is added, and already-started deployments continue.
44
+
45
+ For an existing hourly release process, the optional `productionWindow` setting
46
+ in the same file restricts explicit production admission to configured UTC minutes.
47
+ Outside that window, create and retry return `deployment_window_closed` with the
48
+ next opening time; no attempt starts. This adds no scheduler or preview restriction.
49
+
50
+ SDK 0.33.0 requires `ActionContext.principal.identities`. Code that invokes an action handler
51
+ directly, including unit tests, must provide it (use `identities: []` when there are none),
52
+ plus `createdVia` and `authenticatedVia`. Identity facts use `provider`, `subject`, optional
53
+ `username`, and `verified: true`.
54
+ The general `Principal` and `prepare` context do not include this field.
55
+
56
+ ## Install
57
+
58
+ ```sh
59
+ npm install @segmentstream/ontology effect@4.0.0-rc.113
60
+ ```
61
+
62
+ `effect` is a peer dependency. Install the exact version above: each SDK release supports
63
+ one Effect version.
64
+
65
+ `ontology/` is a package of its own. The SDK is an ES module, so `ontology/package.json`
66
+ must set `"type": "module"`.
67
+
68
+ A deployment applies its ontology whether or not workflows are running. A run that is in flight
69
+ keeps executing the code of the deployment that started it, against the catalog the new deployment
70
+ applied, and current users, roles and revocations are still checked on every action. So change
71
+ what running workflows use in steps: add the new field, action or permission and deploy; move the
72
+ workflows to it; remove what earlier code used in a later deploy, once its runs have finished.
73
+
74
+ Manifest version 15 changes no field. Ontology SDKs 0.86.0 to 0.88.0 introduced it with a deploy
75
+ guard that refused or paused such deployments; the guard was withdrawn. A workspace still deployed
76
+ on one of those SDKs interrupts its own in-flight runs when a later deployment changes the ontology
77
+ beyond labels and descriptions, so upgrade it while no workflow is running.
78
+
79
+ Manifest version 14 names analytics as dbt does: metrics and
80
+ metric queries. Declare `entity(…, { metrics })` and `defineOntology({ metrics })`; query in
81
+ actions with `Order.queryMetrics({ metrics, … })`; the routes are `POST /api/ontology/query_metrics`
82
+ and `POST /api/ontology/<entity>/query_metrics`, with `metrics` and `orderBy[].metric`, and the
83
+ manifest lists `metrics` with `{ op: "metric" }` expression nodes. Code, requests and manifests
84
+ that use the earlier names keep working as deprecated aliases, which the release notes list; a
85
+ request that uses both names for one field is refused. Error codes such as
86
+ `invalid_aggregate_query` and `aggregate_unavailable` are unchanged.
87
+
88
+ SDK 0.37.0 writes manifest version 9, which adds `workflows` (see [Workflows](#workflows)).
89
+ Version 8, since SDK 0.23.0, added the metrics of the model.
90
+ `defineOntology({ metrics })` declares metrics of the model: arithmetic over metrics of entities,
91
+ `m.ref("acmeSales.ordered")`, and over other model metrics, `m.ref("planAttainment")`. `align` names the properties that stand for one another
92
+ across the entities a metric reads, or `null` for an entity without a counterpart, such as a
93
+ current snapshot without a date:
94
+
95
+ ```ts
96
+ defineOntology({
97
+ entities: [AcmeSales, AcmePlans],
98
+ metrics: {
99
+ planAttainment: m.divide(m.ref("acmeSales.ordered"), m.ref("acmePlans.plannedUnits"), {
100
+ align: [{ acmeSales: "orderDate", acmePlans: "month" }],
101
+ unit: "ratio",
102
+ description: "Ordered units over planned units for the same months.",
103
+ }),
104
+ },
105
+ });
106
+ ```
107
+
108
+ Metric queries name a metric of the model by its bare name, `plan_attainment`, beside
109
+ `entity.metric`; a filter must reach every entity it reads unless its alignment says the entity has
110
+ no counterpart, and reading it requires reading each of those entities. `check` refuses aggregates
111
+ inside a metric of the model, unknown references, cycles and alignments that do not name every
112
+ entity once with properties of one type. Entity metrics take no `align`.
113
+
114
+ SDK 0.17.0 writes manifest version 7. It adds `m.avg`, `m.min` and `m.max`; a `where` option on
115
+ every aggregate, so that a metric reads only the rows its conditions accept (`m.sum("amount",
116
+ { where: [{ property: "status", operator: "equals", value: "won" }], description })`); and
117
+ `balanceOver`, which names the date a metric's rows are snapshots over, so that requests group by
118
+ it or filter it to one day. Aggregate requests
119
+ take the `notEquals`, `notIn`, `gt`, `gte`, `lt`, `lte`, `contains`, `startsWith`, `endsWith` and
120
+ `inPeriod` operators, the `hour`, `quarter` and `year` granularities and a `timezone`. See
121
+ [ontology metrics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-metrics.md).
122
+
123
+ SDK 0.14.0 lets the app's own agent call the service as a paired member (see
124
+ [Agents](#agents)). Its migration adds the agent's runtime tables.
125
+
126
+ SDK 0.18.0 lets blob properties store any file. `accept` is optional and can restrict a
127
+ property to exact MIME types or ranges. Existing explicit restrictions keep their meaning.
128
+ See [File storage](#file-storage).
129
+
130
+ SDK 0.12.0 adds warehouse replication enabled by default for all stored business entities,
131
+ fields and business relationships. Existing history captures immutable snapshots and the
132
+ service wakes the publisher after commits. Upgrade and redeploy to activate it; workspace
133
+ configuration can intentionally exclude exported data. Automatic repairs build a private replacement
134
+ while the serving analytical projection keeps receiving changes and answering queries. See
135
+ [replication lifecycle](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-replication.md).
136
+ The next release supports schema changes during active copies: property renames adapt views over retained
137
+ JSON, and restored or newly included data is exported through scoped resumable scans. No manual pause
138
+ is required for deployment or recovery. Explicit opt-outs and supported-writer fences remain in force.
139
+
140
+ SDK 0.13.0 writes manifest version 6. Relationships may target warehouse-backed entities, from
141
+ stored or warehouse-backed entities, matched by value through `on`, and `unique` is allowed on
142
+ warehouse-backed properties as an unenforced assertion. Upgrading: 0.12 accepted an explicit `onDelete`, or `optional: true`, on a
143
+ warehouse → stored relationship; 0.13.0 `check` refuses both, so remove them. See
144
+ [ontology metrics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-metrics.md#relationships).
145
+
146
+ SDK 0.13.0 also serves aggregates. Upgrade notes:
147
+
148
+ - `POST /api/ontology/aggregate`, the MCP tool `aggregate` and, for each entity with metrics,
149
+ `POST /api/ontology/<entity>/aggregate` answer metrics of the model as the caller. The
150
+ service checks `<entity>:read` on every entity a request touches, then forwards it to
151
+ SegmentStream with `SEGMENTSTREAM_APP_CREDENTIAL`. Previews and local development have no
152
+ credential and answer `503 aggregate_unavailable`.
153
+ - `m.countDistinct(property)` counts the distinct values of a text, email, url, select,
154
+ integer, date or timestamp property.
155
+ - Stored entities may declare `metrics`, computed over their replicated records; the built-in
156
+ `User` cannot.
157
+ - `aggregate` is reserved for the route and the MCP tool: rename an entity with that name
158
+ before upgrading.
159
+ - Under `signIn: { policy: "open" }`, anyone who signs in holds `member`, so `check` refuses a
160
+ `member` whose wildcard (`*:read`, the default, or `*:*`) would let strangers read
161
+ warehouse-backed entities through aggregates. Grant those to `member` by name
162
+ (`acme_sales:read`), or keep the `invited` policy.
163
+
164
+ See [ontology metrics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-metrics.md)
165
+ and [ontology analytics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-aggregates-spec.md).
166
+
167
+ ## File storage
168
+
169
+ Use `p.blob({ description: "Original source file." })` to retain a file's original bytes.
170
+ Without `accept`, a blob property accepts every concrete MIME type, including custom formats
171
+ and `application/octet-stream`. `p.blob()` also defaults to all files, though ontology checks
172
+ require a description. The manifest publishes this policy as `accept: ["*/*"]`.
173
+
174
+ Restrict a property when its business rules require a format:
175
+
176
+ ```ts
177
+ content: p.blob({
178
+ description: "Original POS report or bank statement.",
179
+ accept: [
180
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
181
+ "application/vnd.ms-excel",
182
+ "text/csv",
183
+ "text/tab-separated-values",
184
+ ],
185
+ maxBytes: 10 * 1024 * 1024,
186
+ }),
187
+ ```
188
+
189
+ `accept` takes MIME types, not extensions. Matching ignores case; `image/*` accepts image
190
+ types and `*/*` accepts all types. Empty lists, duplicate entries, invalid MIME types and
191
+ parameters such as `; charset=utf-8` are rejected during ontology checks.
192
+ `BLOB_CONTENT_TYPES` lists common types as a convenience; it is not a global allow-list.
193
+
194
+ | Source format | MIME type |
195
+ | --------------------- | ------------------------------------------------------------------------- |
196
+ | XLSX | `application/vnd.openxmlformats-officedocument.spreadsheetml.sheet` |
197
+ | XLS | `application/vnd.ms-excel` |
198
+ | CSV | `text/csv` |
199
+ | TSV | `text/tab-separated-values` |
200
+ | JSON | `application/json` |
201
+ | TXT | `text/plain` |
202
+ | DOCX | `application/vnd.openxmlformats-officedocument.wordprocessingml.document` |
203
+ | ZIP | `application/zip` |
204
+ | Unknown binary format | `application/octet-stream` |
205
+
206
+ Uploads still require a valid concrete MIME type, a nonempty file within the property's
207
+ `maxBytes` and the 250 MiB service limit, available workspace quota and action permissions.
208
+ A refusal for a platform limit is an `action_rejected` body with `limit`: `storage_quota` with
209
+ `used_bytes`, `requested_bytes` and `quota_bytes`; `pending_uploads` with `pending` and `max`; or
210
+ `member_attachments` with `live` and `max`, so an app or agent can explain it in its own words.
211
+ Completion verifies the stored MIME metadata, exact byte count and upload-session binding.
212
+ PDF, JPEG and PNG retain their prefix-signature checks; plain text retains its UTF-8 prefix
213
+ check. Other formats are stored as opaque bytes, without parsing, conversion, archive
214
+ extraction or malware scanning. A MIME declaration does not prove what a file contains.
215
+ Downloads use attachment disposition; only JPEG and PNG may request inline display.
216
+ Agent attachment promotion honors the same `accept` and `maxBytes` restrictions.
217
+
218
+ Server-side `OntologyBackend` extensions can use `blobs.target(uploadId, principal)` to
219
+ inspect a session's persisted entity, action, property, record ID or create intent before
220
+ continuing an upload. It checks the session owner, workspace branch, deployed manifest
221
+ and current member permissions without provider calls or writes. It is not an HTTP
222
+ endpoint and returns no storage key or transfer grant. Custom blob adapters may omit
223
+ this optional method; extensions requiring target inspection must then deny the operation.
224
+
225
+ Upgrade the ontology SDK to 0.18.0 or later, update any existing `accept` list that should
226
+ allow more types, then check, migrate and redeploy the ontology. The app SDK storage client
227
+ 0.13.1 or later uses `application/octet-stream` when the browser supplies no MIME type.
228
+ REST callers should supply that type themselves for unknown formats.
229
+
230
+ See [blob storage](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-blob-storage-implementation.md)
231
+ for upload tickets, transfer grants and retained revisions.
232
+
233
+ SDK 0.56.0 adds server-side file transfers through `Files`. In an action, declare
234
+ `files: [uploads.target(self, "content")]`, stage a stream in `prepare`, then attach the
235
+ returned ticket in `handler`. This uses the same private bucket, upload sessions,
236
+ permissions, quotas and completion checks as browser uploads. `Files.open` streams an
237
+ authorized retained revision for sending to another provider. Use `files: []` when
238
+ preparation only reads files and has no connections. Callers of staging actions provide
239
+ an idempotency key; workflow action calls derive a stable key from the run, step and input
240
+ when omitted. Streams stay within the step; carry tickets and references between steps.
241
+ See [server-side file transfers](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-blob-storage-implementation.md#server-side-file-transfers)
242
+ for the API and recovery rules.
243
+
244
+ Buffered `get`/`post`/`request` calls receive the provider's response through a streamed
245
+ proxy call and collect up to 64 MiB (`MAXIMUM_CONNECTION_RESPONSE_BYTES`); a larger response
246
+ fails with `invalid_response`. To process a response incrementally, or one larger than that,
247
+ use `connection.stream({ method: "GET", path })` inside `Effect.scoped` and consume its `body`
248
+ as an Effect Stream before closing the scope. Request bodies remain capped at 2 MiB.
249
+
250
+ ## Define the ontology
251
+
252
+ Actions run their handler at most once by default. Declare `retryOnDeadlock: true`
253
+ in `action({ ... })` only when its handler and any wrappers have solely
254
+ transactional database effects, treat input and prepared data as immutable, and
255
+ recreate mutable state per invocation. Trigger predicates and mappings must be
256
+ pure. The default SQL runtime retries a confirmed PostgreSQL `40P01` at most twice,
257
+ after completed rollback and connection release, with short randomized backoff
258
+ inside the original handler deadline and admission slot. It rebuilds runtime,
259
+ edit and trigger state while preserving action and request identity. `prepare`
260
+ runs once; workflow dispatch and after-commit hooks run after the successful
261
+ commit. They are never replayed. This declaration is independent of
262
+ `hints.idempotent`, appears in the deployed action manifest, and does not retry
263
+ timeouts, cancellation, permission failures, cleanup failures or uncertain commits.
264
+ Custom service runtime implementations own their retry policy; wrappers around
265
+ the default backend are not replayed. See
266
+ [action deadlines and load](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology.md#action-deadlines-and-load)
267
+ for the full replay contract.
268
+
269
+ An action with `hints: { connectionsReadOnly: true, destructive: false }` may read
270
+ providers in `prepare` and write local records in `handler`. All its connection
271
+ clients enforce GET/HEAD/OPTIONS without bodies or method overrides. MCP treats
272
+ these provider reads as closed-world; destructive or irreversible local writes
273
+ still require confirmation. This differs from `readOnly: true`, which requires
274
+ a prepare-only action and permits no local writes.
275
+
276
+ An action that agents confirm can describe its own confirmation card with `confirm`: a
277
+ read-only function of the decoded input, the principal and `locale`, the language of the chat
278
+ the agent asks in. It runs after the permission check, with reads and no writes, and returns
279
+ the card's `title` (a verb phrase), an optional `icon` emoji, the `facts` the person checks
280
+ before confirming (one line each, optionally linked with an https `url`), collapsed `details`
281
+ and an optional page `link`. All of it is plain text in that language, without ids, codes or
282
+ markup: these are the workspace's words, and agents refuse a card that is not. Fail with
283
+ `ActionRejected` when the action would refuse this input: the agent then tells the person why
284
+ instead of asking. Without `confirm`, agents show the action's fields.
285
+
286
+ ```ts
287
+ send: action({
288
+ description: "Send the envelope for signature.",
289
+ hints: { destructive: false, irreversible: true },
290
+ input: Schema.Struct({ envelope: self.ref }),
291
+ confirm: ({ input, locale }) =>
292
+ Effect.gen(function* () {
293
+ const envelope = yield* self.get(input.envelope);
294
+
295
+ if (envelope.status !== "draft") {
296
+ return yield* new ActionRejected({ message: texts(locale).alreadySent });
297
+ }
298
+
299
+ return {
300
+ title: texts(locale).send,
301
+ icon: "✍️",
302
+ facts: [{ icon: "📄", text: envelope.title }],
303
+ details: [texts(locale).emailNow],
304
+ };
305
+ }),
306
+ handler: ({ input }) => self.update(input.envelope, { status: "sent" }),
307
+ }),
308
+ ```
309
+
310
+ ```ts
311
+ // ontology/lead.ts
312
+ import { action, ActionRejected, entity, p, rel, role, User } from "@segmentstream/ontology";
313
+ import { Effect, Schema } from "effect";
314
+
315
+ export const sales = role("sales", {
316
+ description: "Works leads to a pilot.",
317
+ permissions: ["lead:read", "lead:win"],
318
+ });
319
+
320
+ export const Lead = entity("lead", {
321
+ id: "ent_01J9Q4M8ZK",
322
+ label: "Lead",
323
+ plural: "Leads",
324
+ title: "email",
325
+ description: "A person who asked for a pilot.",
326
+ properties: {
327
+ email: p.email({ unique: true, description: "The address they submitted." }),
328
+ status: p.select(
329
+ [
330
+ { value: "new", label: "New", description: "Nobody has replied yet." },
331
+ { value: "won", label: "Won", description: "They became a customer. Final." },
332
+ ],
333
+ { default: "new", managed: true },
334
+ ),
335
+ },
336
+ relationships: {
337
+ owner: rel.one(User, {
338
+ optional: true,
339
+ inverse: "ownedLeads",
340
+ onDelete: "setNull",
341
+ phrase: ["is owned by", "owns"],
342
+ description: "The team member responsible for the next step.",
343
+ }),
344
+ },
345
+ }).actions((self) => ({
346
+ win: action({
347
+ description: "Mark the lead as won.",
348
+ input: Schema.Struct({ lead: self.ref }),
349
+ // Requires lead:win, the default: <entity>:<action name>.
350
+ handler: ({ input }) =>
351
+ Effect.gen(function* () {
352
+ const lead = yield* self.get(input.lead, { forUpdate: true });
353
+
354
+ if (lead.status === "won") {
355
+ return yield* new ActionRejected({ message: "The lead is already won." });
356
+ }
357
+
358
+ yield* self.update(lead, { status: "won" });
359
+ }),
360
+ }),
361
+ }));
362
+ ```
363
+
364
+ ```ts
365
+ // ontology/index.ts
366
+ import { defineOntology } from "@segmentstream/ontology";
367
+
368
+ import { Lead, sales } from "./lead";
369
+
370
+ export default defineOntology({
371
+ entities: [Lead],
372
+ roles: [sales],
373
+ // What every active app user may do. Without it, members may only read: ["*:read"].
374
+ member: { permissions: ["*:read"] },
375
+ // Who may become a member by signing in: only invited people (the default), or anyone ("open").
376
+ signIn: { policy: "invited" },
377
+ });
378
+ ```
379
+
380
+ - `entity(name, { … })` declares an entity. `id` is its stable identity; do not change it.
381
+ - `p.text`, `p.email`, `p.url`, `p.integer`, `p.number`, `p.boolean`, `p.date`,
382
+ `p.timestamp`, `p.select` and `p.json` declare properties. `managed: true` keeps a
383
+ property out of the generated `update` action.
384
+ - `rel.one` and `rel.many` declare relationships. Between stored entities, `rel.one` adds a
385
+ key and `rel.many` a join table. With a warehouse-backed end, `rel.one` declares one `on`
386
+ pair and is matched by value. `User` is the built-in entity for app users and service
387
+ principals; `AgentChat` (`agent_chat`) is the built-in entity for the group chats of the
388
+ app's agent (see [Agents](#agents)). Every ontology includes both. Text feedback defaults on with built-in `AppFeedback`; files follow the non-feedback-blob policy
389
+ (or an explicit opt-in) and add `AppFeedbackFile` (see [App feedback](#app-feedback)).
390
+ - `role(name, { description, permissions })` declares a role: a named set of permissions.
391
+ `member` is built in: every active app user holds it, with the permissions of
392
+ `defineOntology({ member })`.
393
+ - `.actions((self) => ({ … }))` adds actions. Each action has a description, an input
394
+ schema and an Effect handler. It requires the permission `<entity>:<action name>`, or the
395
+ one its `permission` names.
396
+ - `signIn.browser` optionally declares `{ methods: ["email", "segmentstream"], primary: "email", autoRedirect: false, local: { port: 3000 } }`.
397
+ Methods must be nonempty and unique, and primary must be enabled. Automatic redirect is allowed only
398
+ for sole SegmentStream authentication. Local OIDC is optional and requires SegmentStream plus a port
399
+ from 1 through 65535. Omission preserves existing authentication. Deployment derives the versioned
400
+ `SEGMENTSTREAM_BROWSER_AUTH` binding from manifest capability 12; mismatches fail closed.
401
+ SegmentStream enrollment claims only active unclaimed invitations using a verified provider email;
402
+ established members require explicit independent login proof. An OIDC member can explicitly attach
403
+ email authentication in dual mode without changing their member ID. Reverse linking requires the
404
+ independently verified email to match their canonical workspace email; missing or different email
405
+ is rejected as `email_identity_mismatch`. Forward email-to-OIDC linking may use a different account
406
+ email because it proves both identities explicitly. Keep OIDC available until every
407
+ affected member has linked email before restoring an email-only deployment. `User.sendSignInLink`
408
+ preserves Neon links in compatibility and dual mode; sole SegmentStream uses interactive central
409
+ sign-in, preserving templates, reply routing and the requested landing path.
410
+ - `signIn: { policy }` decides who may become a member by signing in. `invited`, the
411
+ default, admits only people whose email a member already has, from an invitation or a
412
+ role grant; anyone else is answered 403 `not_invited`. `open` makes anyone who signs in a
413
+ member holding only `member`. The manifest carries the policy.
414
+ `signIn.codeLink: true` makes the platform's sign-in code emails also link to the app's
415
+ `/sign-in` page with the code in the URL fragment; the app SDK's `useSignIn()` fills it in and
416
+ waits for the person to confirm. Off when omitted; see the app authentication guide.
417
+ Explicit app opt-in (never a platform or starter default): channel admission is independent: `signIn: { policy: "invited", channels: { telegram: "open" } }`
418
+ lets private channel senders become email-less members. Missing channels require pairing; email
419
+ `policy: "open"` never opens a channel. `CHANNEL_PROVIDERS` lists supported providers (`telegram`).
420
+ Every configured channel requires a matching `chat.<provider>` adapter; object values are reserved.
421
+ Open channels require explicit `member.permissions`: entity wildcards and generated create/update/delete
422
+ grants (including grants through entity `:*`) are errors. Named shared reads warn for review; public
423
+ catalogs are valid. The hint is “move staff reads into a named role”. Actions such as
424
+ `updateMine` must check ownership with `principal.id`. Closing a channel stops new registrations;
425
+ existing members keep working. Blocking is sticky and independent of activation; deleting a member releases identities.
426
+ `signIn.onNewUser: "person:ensureMine"` optionally names an app-owned onboarding action. It must exist,
427
+ accept `{}`, be runnable by `member`, declare `hints.idempotent: true`, and have no `prepare` or
428
+ external connections. Its handler must have no external effects. The runtime runs it as the new user
429
+ with only member permissions after admission commits, before the first model turn or Mini App session.
430
+ Handler writes and its completion marker commit together, under a five-second deadline. Failure logs
431
+ diagnostics and leaves it pending for retry on the next eligible contact; admission still succeeds.
432
+ The service shares one action admission gate between onboarding and ordinary actions, reserving
433
+ database capacity for other requests before either kind of handler acquires a connection.
434
+ Invited/paired-only members never run this hook. The platform supplies mechanism; the app owns channel
435
+ policy, grants, hook content, notice text, and lower limits. Nothing defaults to open.
436
+ `check(config, { agentConfig })` warns if open channels lack `firstContact.notice` or `limits` when
437
+ agent config is supplied. The ontology CLI cannot see that config and says those checks are skipped.
438
+ - `emails: { agreementReview: { subject, eyebrow?, heading, card?, note?, paragraphs?, action, footer?, replyTo? } }`
439
+ declares the workspace's own emails, in plain text with `{{value}}` placeholders and the
440
+ built-in `{{member.name}}`, `{{member.firstName}}` and `{{workspace.name}}`. A handler sends
441
+ one with `User.sendSignInLink(user, { template: "agreementReview", returnTo: "/shared/1", values })`:
442
+ after the commit, in production, the platform emails the member a link that signs them in and
443
+ opens `returnTo`, laid out in its design, from the workspace's name, with replies to `replyTo`
444
+ or else the member who ran the action. Without `template` the
445
+ member gets the platform's own invitation or sign-in email. `check` reports `invalid-email`.
446
+ - `defineOntology` checks the definition and builds the manifest. It throws
447
+ `InvalidOntology` with every diagnostic when the definition has errors. Entities,
448
+ actions and select options need a description.
449
+
450
+ ## Workflows
451
+
452
+ A workflow is a durable process over time, written with the
453
+ [Vercel Workflow SDK](https://useworkflow.dev): it can wait, retry a step, run several actions and
454
+ call connections. The destination decides the home, not the data source: work that outputs
455
+ tables is a job in `pipeline/`; records change through actions; work that runs on its own over
456
+ time and outputs records, paged provider reads included, is a workflow in `ontology/workflows/`.
457
+ Declare one per
458
+ file with `workflow` and `cron` from `@segmentstream/ontology/workflows`, and list them in
459
+ `defineOntology({ workflows })`:
460
+
461
+ ```ts
462
+ // ontology/workflows/remind-stale-leads.ts
463
+ import { cron, p, run, workflow } from "@segmentstream/ontology/workflows";
464
+ import { Effect, Schema } from "effect";
465
+ import { Lead } from "../lead";
466
+
467
+ async function remindStaleLeads(input: { olderThanDays: number }) {
468
+ "use workflow";
469
+ const leads = await findStale(input.olderThanDays);
470
+ for (const lead of leads) await remind(lead.id);
471
+ }
472
+
473
+ async function findStale(olderThanDays: number) {
474
+ "use step";
475
+ const before = new Date(Date.now() - olderThanDays * 86_400_000).toISOString();
476
+
477
+ return run(
478
+ Effect.map(
479
+ Lead.find({ where: { status: "new", updatedAt: { lt: before } }, limit: 100 }),
480
+ (leads) => leads.map((lead) => ({ id: lead.id })),
481
+ ),
482
+ );
483
+ }
484
+
485
+ async function remind(leadId: string) {
486
+ "use step";
487
+ return run(Lead.actions.addNote({ lead: leadId, body: "No movement for three days." }));
488
+ }
489
+
490
+ export default workflow("remindStaleLeads", {
491
+ description: "Write a reminder note on every lead that has not moved.",
492
+ role: "reminders",
493
+ input: Schema.Struct({ olderThanDays: p.integer({ description: "Days without movement." }) }),
494
+ run: remindStaleLeads,
495
+ triggers: [
496
+ cron("weekdayMorning", "0 8 * * 1-5", {
497
+ description: "Every weekday at 08:00 UTC.",
498
+ input: { olderThanDays: 3 },
499
+ }),
500
+ ],
501
+ });
502
+ ```
503
+
504
+ - Import what the file uses at its top level from `@segmentstream/ontology/workflows`,
505
+ which re-exports `p` for the input, and `Schema` from `effect`. The Workflow SDK bundles a
506
+ workflow file's top-level imports for a runtime without Node built-ins; this entry, and entity
507
+ and action declarations, import none, while the package root does. Steps may use anything:
508
+ the SDK strips their bodies from that bundle.
509
+ - `workflow(name, { description, role, input?, run, triggers?, connections? })` only records
510
+ the declaration; `check` and the evaluation of `index.ts` never call `run`, and the
511
+ `"use workflow"` and `"use step"` directives are inert strings until the Workflow SDK's
512
+ compiler builds the service (`ontologyWorkflows()` in `ontology/nitro.config.ts`, below).
513
+ Keep workflow files in the directories it compiles (`./workflows` by default): a `run` the
514
+ SDK did not compile is logged as `ontology.workflow.not_compiled` when the service starts, and
515
+ starting it answers 503 `workflow_not_compiled`.
516
+ - `name` is camelCase and unique; it names the workflow's user. `description` is required.
517
+ - `role` is a declared role other than `member`, and not one that grants `*:*`. The runs hold
518
+ what it and `member` grant.
519
+ - `input` is a `Schema.Struct`; omitted means `{}`. `run` receives the decoded input in
520
+ TypeScript names, and as its second argument `{ startedBy }`: the trigger, the member, or the
521
+ platform (with the person it acted for) that started the run.
522
+ - `triggers` lists what starts the workflow on its own. Each carries a `kind`: `cron`,
523
+ `action` or `webhook`. `cron(name, expression, { description, input? })` takes five fields in UTC, each `*`, numbers,
524
+ ranges, steps after `*` or a range (`*/5`, `0-30/10`; not `5/15`) or lists, with at most one of
525
+ day-of-month and day-of-week restricted and no month or day names, as Vercel accepts them. Its `input` is
526
+ written in TypeScript names and must decode with the workflow's `input`. Trigger names are
527
+ camelCase, `<workflow>.<trigger>` on the wire (`remind_stale_leads.weekday_morning`).
528
+ - `after(name, target, { description, when?, input? })` starts the workflow after an action
529
+ commits: `target` is an action handle (`Lead.actions.changeStatus`) or an entity handle (`Lead`,
530
+ any of its actions). `when` and `input` are synchronous functions of the event
531
+ (`{ action, actionId, at, principal, input, result, edits }`) and the record
532
+ (`{ record, before, after }`, typed as the entity's records); `input` omitted means `{}`.
533
+ `changed(field, { from?, to? })` is a `when` for the common case. A workflow reacts to an action,
534
+ not to a field. The manifest records the kind, name, description, entity and action, never the
535
+ functions. At most 20 action triggers per ontology; `check` reports `invalid-trigger`,
536
+ `trigger_action_unknown`, `trigger_function_async` and `workflow_trigger_limit_exceeded`.
537
+ - `webhook(name, { description, signature?, headers? })` starts the workflow for each delivery a
538
+ provider pushes to `/api/webhooks/<name>`, whose wire name is unique across the ontology. The
539
+ workflow's `input` is `webhookDelivery` (`body`, `contentType`, `query`, `headers`,
540
+ `receivedAt`) and it has only webhook triggers. Without `signature`, the URL ends in a token
541
+ derived from the workspace's webhook secret; with `{ scheme: "stripe", secret }`, each delivery's
542
+ signature is checked against that workspace secret through SegmentStream. `headers` lists up to
543
+ 10 request headers the delivery carries. The route answers `200` once the run started, `404`,
544
+ `401`, `413` or `415` to refused deliveries and `503` when the run could not start. Production
545
+ only. `check` reports the `workflow_webhook_*` codes; at most 20 webhooks per ontology.
546
+ - `connections` lists the connections the steps call directly; each must be in
547
+ `defineOntology({ connections })`. Connections declared on an action stay that action's.
548
+ - At most 50 workflows. `check` reports `workflow_role_unknown`, `workflow_role_is_member`,
549
+ `workflow_role_grants_nothing` (a warning: with member's grants, the role grants no action),
550
+ `workflow_role_wildcard` (the role grants `*:*`), `workflow_input_invalid`,
551
+ `workflow_cron_invalid`, `workflow_cron_never_fires` (a warning: no listed month has the
552
+ day), `workflow_run_missing` (`run` is not a function), `invalid-workflow`, `invalid-property`
553
+ (a property option its constructor does not take), and the usual name, duplicate and
554
+ description checks.
555
+
556
+ ### Code inside a step
557
+
558
+ Inside a `"use step"` function, `run(effect)` runs an Effect with the ontology's runtime as its
559
+ services, bound to the run's workflow user. Entity handles work as in an action handler:
560
+ `Lead.find` and `Lead.get` read, `Lead.create`, `update`, `upsertBy`, `delete`, `link` and
561
+ `unlink` run the generated actions, and `Lead.actions.addNote(input)` runs the authored action
562
+ with its input in TypeScript names; so do the workflow's `connections`. Every read and action goes
563
+ through the same path as the HTTP routes: it is checked against the workflow user's permissions,
564
+ validated, and recorded in history under the workflow user. Batch writes (`createMany`,
565
+ `updateMany`, `upsertMany`) are refused in steps; run an action that writes the batch. In production,
566
+ workflow roles can explicitly grant `agent_chat:post_message` and `agent_chat:preview_message` for
567
+ attributed chat delivery and its destination preview. Preview deployments refuse both actions;
568
+ agent tasks cannot post to chat on either target.
569
+
570
+ A step calls a model through Vercel AI Gateway with the AI SDK (`ai`), outside `run`: the
571
+ deployment authenticates with its Vercel project identity, so the workspace holds no key. Give
572
+ each model call a step of its own that returns its result, and store the result with an action
573
+ in the next step, since a retried step calls the model again. A workspace's spent Gateway budget
574
+ refuses the call with `402`. The ontology SDK does not wrap these calls.
575
+
576
+ `run` resolves with the effect's value, which the step returns, so keep it JSON-serializable.
577
+ A failure rejects: a typed failure of the handles (`NotFound`, `Conflict`, `ActionRejected`,
578
+ `PermissionDenied`, `ConnectionError`) becomes an `Error` named after it with its message and the
579
+ request id; anything else becomes a generic message with the request id, and the service logs
580
+ the full cause as `ontology.workflow.step.failed`. The Workflow SDK records the failed step and
581
+ retries it, except a refusal that would repeat (`PermissionDenied`, `ActionRejected`, an invalid
582
+ input), which is marked fatal. A read the role does not grant fails the step. Calling `run` in
583
+ the `"use workflow"` body or outside a run throws `WorkflowStepRequired`. `run` reads the
584
+ step from the Workflow SDK's process-wide step context and never imports the SDK, which a
585
+ deployed function need not be able to resolve.
586
+
587
+ A retried step runs its actions again: the SDK has no idempotency for actions, so write steps
588
+ whose actions tolerate a repeat (an `upsertBy`, a check before a write), as the example's
589
+ `submitLead` does with its submission id.
590
+
591
+ The binding: when the ontology service builds its routes it installs, on `globalThis`, the
592
+ runtime that serves this process's runs, with the services the routes run with; the step
593
+ callback builds the service first when this instance has not. Inside a step, `run` reads the run
594
+ from the Workflow SDK's step context (`getWorkflowMetadata()`), finds the declared workflow whose
595
+ compiled `run` has the message's workflow id, and checks it against the run's own record in the
596
+ world (`runs.get(runId).workflowName`, read once per run), refusing a step whose workflow is not
597
+ its run's; a run the world does not show yet fails the step, which is retried. It then resolves
598
+ the workflow's user and provides the runtime bound to it. No token is
599
+ involved; connection calls carry the callback request's Vercel OIDC token
600
+ (`x-vercel-oidc-token`), else the deployment's.
601
+
602
+ ### Agent tasks
603
+
604
+ A workflow hands the workspace's agent a prompt with `agentTask` from
605
+ `@segmentstream/ontology/workflows`, in its `"use workflow"` body, and waits for the
606
+ agent's final answer without holding compute. It is the agent members reach in chats, with the
607
+ same instructions, models, tools and sandbox, acting as the workflow's own user: it sees and does
608
+ exactly what the workflow's role and `member` grant, and ontology actions run without
609
+ confirmation, since no person is present. Keep the role narrow: a prompt may carry untrusted text.
610
+
611
+ ```ts
612
+ import { agentTask, workflow } from "@segmentstream/ontology/workflows";
613
+
614
+ async function draftReply(input: { message: string }) {
615
+ "use workflow";
616
+ const prompt = await describeMessage(input.message); // a step that reads records
617
+ const result = await agentTask({ prompt, timeoutMs: 15 * 60_000 });
618
+
619
+ if (result.status === "completed") {
620
+ await saveDraft(input.message, result.text); // a step that runs an action
621
+ }
622
+ }
623
+ ```
624
+
625
+ - `prompt` is 1 to 32,768 characters, all of it the workspace's: nothing is added to it.
626
+ `timeoutMs` defaults to 900,000 (15 minutes) and is 60,000 to 3,600,000. Invalid options fail
627
+ the workflow with `AgentTaskOptionsInvalid`, which names the option, and a call outside a
628
+ `"use workflow"` body (in a step, or outside a workflow) with `AgentTaskWorkflowRequired`;
629
+ neither is retried.
630
+ - It resolves with `{ status: "completed", text, truncated }`, the agent's final text;
631
+ `{ status: "needs_input", text, truncated }`, its question, when the agent would need a person;
632
+ `{ status: "failed", reason }` with `agent_unavailable` (the deployment has no agent, its agent
633
+ predates tasks, or the run is outside production), `busy` (the workspace has 10 live tasks; the
634
+ agent was not called) or `agent_failed` (its turn failed, its cost cap included); or
635
+ `{ status: "timed_out" }`. Text beyond 65,536 characters is cut and marked `truncated`.
636
+ - A task cannot post to chat in this release: `agent_chat.post_message` is refused for a task
637
+ whatever the role grants. The answer reaches people through the workflow's later steps.
638
+ - At least once: a start step retried after the agent accepted the task hands it over again, and
639
+ the agent ends the earlier session. Write prompts and later steps that tolerate a repeat.
640
+ - Tasks run only in production, where the agent's runtime tokens are valid. Locally and in
641
+ previews `agentTask` resolves with `agent_unavailable`.
642
+
643
+ `agentTask` creates a Workflow SDK hook and runs its start step, one of two steps the package
644
+ ships; the Workflow SDK finds and compiles them through `@segmentstream/ontology/workflows`
645
+ with the workflow's own. The step records the task in `_agent_tasks` under the hook's token,
646
+ mints its secret (32 random bytes, base64url, of which only the SHA-256 is stored; it is never
647
+ returned, logged or written to the run's event log) and posts `{ task_id, secret }` to the
648
+ agent's `POST /eve/segmentstream/v1/tasks` at `https://$VERCEL_PROJECT_PRODUCTION_URL`, with the
649
+ service's own Vercel OIDC token. `202` starts the task and `404` ends it as `agent_unavailable`;
650
+ no answer, `5xx`, and a `409` while the task is still pending fail the step, and the retry hands
651
+ the pending task over with a new secret. A retry finds a started or ended task and does not hand
652
+ it over again; any other answer ends the task as `agent_unavailable` and fails the step without a
653
+ retry. The workflow then races the hook against a sleep until the task's deadline, `timeoutMs`
654
+ after the start created it, so start-step retries count against the timeout. The agent's report through the task routes (see [Agents](#agents))
655
+ ends the task, the first report winning, and resumes the hook; if the sleep wins, the second step
656
+ times the task out unless it already ended and returns its outcome, so the workflow's result is
657
+ always the task's stored outcome. Each start deletes the tasks whose deadline passed 30 days ago.
658
+
659
+ ### Runtime and routes
660
+
661
+ `ontology/nitro.config.ts` lists `ontologyWorkflows()` from `@segmentstream/ontology/nitro`
662
+ in `modules`, in place of the SDK's own `workflow/nitro`:
663
+
664
+ ```ts
665
+ import { ontologyRoutes, ontologyWorkflows } from "@segmentstream/ontology/nitro";
666
+ import { defineNitroConfig } from "nitro/config";
667
+
668
+ export default defineNitroConfig({
669
+ compatibilityDate: "2026-09-11",
670
+ serverDir: false,
671
+ routes: ontologyRoutes("./server/handler.ts"),
672
+ plugins: ["./server/plugin.ts"],
673
+ modules: [ontologyWorkflows({ dirs: ["./workflows"] })],
674
+ });
675
+ ```
676
+
677
+ It adds the SDK's transform to the server build and, before each build, bundles the workflows of
678
+ `dirs` and their steps into Nitro's build directory, with `/api/ontology` as the SDK's base path
679
+ and `ontology` as its queue namespace, both written into the generated entrypoint at build time
680
+ (no environment variable). It mounts one route in the ontology service's own server bundle,
681
+ `/api/ontology/.well-known/workflow/v1/flow`: since Workflow SDK 5 the flow handler runs the
682
+ steps itself, and background steps come back on the workflow topic. The route answers only a
683
+ `POST` queue delivery of the ontology's queue (`ce-vqsqueuename`, or the local world's
684
+ `x-vqs-queue-name`, starting with `__ontology_wkf_workflow_`) at its exact path, a trailing slash
685
+ refused; anything else is a 404. Before the first step of an instance it builds that instance's
686
+ ontology service through Nitro's own fetch of `/api/ontology/status`; a failure fails the delivery
687
+ for the queue to retry. On Vercel the route gets its own function with `maxDuration: "max"` and
688
+ the queue trigger `__ontology_wkf_workflow_*`; runs start on the same queue (`start` with the
689
+ namespace `ontology`). The app's own workflows use the SDK's default topics and the agent's use
690
+ topics of its own, so none of them receive another's messages. Nitro states that routes with queue
691
+ triggers are not reachable from the web; that is still to be confirmed on a deploy. If they are
692
+ reachable, the queue-header and run-record checks still refuse a request that is not the
693
+ ontology's own delivery.
694
+
695
+ Webhooks are not supported yet: no webhook route is mounted, since this version of the SDK derives
696
+ a webhook's token from the run. Hooks resumed from server code with `resumeHook` work, as agent
697
+ tasks' do.
698
+
699
+ `server/plugin.ts` calls `installWorkflowRuntime()` from `@segmentstream/ontology/server`
700
+ at startup: until the flow handler loads and sets the base path from the build, it sets it to
701
+ `/api/ontology`, so that runs started first call back there, and it registers the Workflow SDK,
702
+ loaded on first use, for the workflow routes. It also sets `WORKFLOW_QUEUE_NAMESPACE` to
703
+ `ontology` in the service's process, which serves only the ontology's queue: `resumeHook` takes
704
+ no namespace and enqueues the resumed run on the one this variable names. Only that entry imports the Workflow SDK: the package
705
+ root does not, so a process that bundles the service on another Workflow SDK major carries none of
706
+ it, and neither does `@segmentstream/ontology/workflows`, which the Workflow SDK bundles with
707
+ every workflow file.
708
+
709
+ | Route | Who | What |
710
+ | --------------------------------------------------------- | ---------------------------------------------------- | --------------------------------------------------------------- |
711
+ | `GET /api/ontology/workflows/:workflow/triggers/:trigger` | Vercel cron, `Authorization: Bearer $CRON_SECRET` | Starts the workflow with the trigger's input; 202 `{ runId }`. |
712
+ | `POST /api/ontology/workflows/:workflow/start` | Holders of `workflow:<workflow>:start`; the platform | Starts it with `{ "input": … }` in wire names; 202 `{ runId }`. |
713
+ | `GET /api/ontology/workflows` | Any caller; each sees the workflows it may read | The declarations, each trigger with its `path` and `nextAt`. |
714
+ | `GET /api/ontology/workflows/:workflow/runs` | Holders of `workflow:<workflow>:read`, the platform | The Workflow SDK's runs, newest first; `?limit` and `?after`. |
715
+ | `GET /api/ontology/workflows/:workflow/runs/:runId` | As above | One run with its steps. |
716
+
717
+ `CRON_SECRET` is read from the service's environment; without it, the trigger routes refuse every
718
+ request. Workflow permissions, `workflow:<workflow>:start` and `workflow:<workflow>:read`
719
+ (`workflow:*:read`, `workflow:<workflow>:*`), are granted by roles like action permissions; only
720
+ a permission that starts with `workflow:` grants them, so `*:*` and `*:read` do not. The
721
+ platform authenticates with a workload token whose subject is `platform:<workspace id>`; it is
722
+ admitted to these routes only, and its start requests may name `startedBy`, which the run
723
+ receives. The deployment's runtime callers hold what the member they act for holds, and never
724
+ start runs. Vercel may deliver a cron invocation more than once, and `start` does not deduplicate:
725
+ a trigger's workflow tolerates a repeated run. A body that is not JSON answers 400.
726
+
727
+ Every route takes the workflow's TypeScript name or its wire name, `remindStaleLeads` or
728
+ `remind_stale_leads`, and so does the trigger segment; the start body's `input` is in wire names, as
729
+ an action's input is.
730
+
731
+ Pages take `?limit` (1–100, default 20) and `?after`, the previous page's `nextCursor`, which is
732
+ `null` on the last page. Success bodies use camelCase; errors are the service's usual
733
+ `{ error, detail, request_id }`. `tests/fixtures/ontology-workflow-service.json` at the repository
734
+ root pins every body, for the CLI and backend-next.
735
+
736
+ Run records come from the Workflow SDK's world (`runs.list`, `runs.get`, `steps.list`) with
737
+ the provider's status, times and safe error diagnostics. Run detail includes `deploymentId` when
738
+ the provider recorded it. Steps are paged newest first, up to 100 per request: `stepsNextCursor`
739
+ reads older steps. Attempt history follows provider event cursors within a 10,000-event budget.
740
+ `attemptsTruncated: true` means history or error details are incomplete or unavailable, including
741
+ provider/encryption failures; an empty history is not proof no attempt ran. When the world cannot
742
+ list a run's steps, detail retains the run with `steps: null`. Failure events resolve errors without
743
+ loading workflow inputs or outputs. Operator logs retain the complete redacted causes, while public
744
+ diagnostics contain safe codes, messages and correlation references.
745
+
746
+ Runs that ontology SDKs 0.86.0 to 0.88.0 failed with `DeploymentInterrupted` or
747
+ `DeploymentSwitching` stay readable, with `category` `deployment_interrupted` or
748
+ `deployment_switching`. Permission revocation remains terminal. Historical catalog mismatches stored
749
+ as `PermissionDenied` retain their original code and add `category: "deployment_interrupted"` and
750
+ `reason: "legacy_catalog_mismatch"`; reading them does not restart the failed run. Failure envelopes
751
+ from older deployments remain readable.
752
+
753
+ Durable admission journals the decoded arguments with a versioned Workflow value encoding,
754
+ preserving dates, bigints, undefined, special numbers, collections and supported shared references
755
+ and cycles. Recovery replays the original arguments, compiled workflow, routing and attributes
756
+ without decoding them against a newer declaration. Historical plain JSON argument rows remain
757
+ readable. Live streams, HTTP request/response objects, abort handles and unsupported custom values
758
+ are refused before commit with `workflow_arguments_unsupported`; registered Workflow serde classes
759
+ must round-trip deterministically with the installed runtime.
760
+
761
+ ### Workflow users
762
+
763
+ Each workflow runs as its own keyless service user: name = the workflow's wire name,
764
+ `identity_provider` `segmentstream-ontology-workflow`, subject
765
+ `workflows:<workspace id>:<workflow name>`, roles = exactly the declared role, no API key. The
766
+ migration that applies the ontology creates it: `applyMigration` given the workspace's
767
+ `workspaceId` runs `reconcileWorkflowUsers` in the transaction that records the manifest, under
768
+ the migration lock, also on a no-op plan, and `segmentstream-ontology migrate` does the same when
769
+ `SEGMENTSTREAM_WORKSPACE_ID` is set. It deactivates the users of removed workflows and
770
+ reactivates redeclared ones. A run before the deploy creates a missing user
771
+ (`resolveWorkflowUser`), but a run never changes roles: the deployed database is the authority,
772
+ and a user whose roles are not exactly the declared role is refused (`role_mismatch`) until a
773
+ deploy reconciles it. Runs that are in flight on an older deployment when a deploy changes the
774
+ workflow's role fail fatally at their next step, since their deployment declares the old role. The
775
+ user never holds `member` as a role; every action re-reads its roles in
776
+ the action's transaction and refuses any assignment other than the declared role.
777
+
778
+ ## Batch writes in actions
779
+
780
+ Stored entity handles expose `createMany(records, options?)`,
781
+ `updateMany([{ id, values }], options?)`, and `upsertMany({ on, records, returning? })`
782
+ inside authored action handlers. The action remains the permission and commit
783
+ boundary; a batch does not run other authored actions for its rows.
784
+
785
+ For example, an action can write sales plan lines by a declared compound unique
786
+ key:
787
+
788
+ ```ts
789
+ const PlanLine = entity("planLine", {
790
+ id: "ent_plan_line",
791
+ label: "Plan line",
792
+ plural: "Plan lines",
793
+ title: "planCode",
794
+ description: "A sales plan decision for a period.",
795
+ properties: {
796
+ planCode: p.text({ description: "The plan code." }),
797
+ period: p.date({ description: "The period start." }),
798
+ target: p.number({ description: "The agreed target." }),
799
+ },
800
+ indexes: [{ properties: ["planCode", "period"], unique: true }],
801
+ }).actions((self) => ({
802
+ setTargets: action({
803
+ description: "Set targets for several plan lines together.",
804
+ input: Schema.Struct({
805
+ lines: Schema.Array(Schema.Struct(self.properties)),
806
+ }),
807
+ handler: ({ input }) =>
808
+ self.upsertMany({
809
+ on: ["planCode", "period"],
810
+ records: input.lines,
811
+ returning: "summary",
812
+ }),
813
+ }),
814
+ }));
815
+ ```
816
+
817
+ Both result modes return `{ created, updated, unchanged }`, whose counts sum to
818
+ the input length. The default, `returning: "records"`, also returns `items` in
819
+ input order: `{ index, status, record }`, with `status` equal to `"created"`,
820
+ `"updated"`, or `"unchanged"`. `returning: "summary"` omits `items`; the enclosing
821
+ action still returns its normal `edits`. These types are exported as
822
+ `BatchRecordResult<Record>`, `BatchSummaryResult`, and `BatchResult<Record>`.
823
+
824
+ Creates retain normal defaults and validation. Updates patch distinct IDs:
825
+ omitted fields remain unchanged, and explicit `null` follows the property's
826
+ nullability. An unchanged patch preserves timestamps and attribution and adds no
827
+ history or replication work. Upsert keys must be a nonempty list of ordinary
828
+ properties matching a declared unique property or compound unique index. Each
829
+ input explicitly supplies non-null key values and enough values to create a
830
+ record; existing records receive only supplied non-key fields. Duplicate IDs or
831
+ upsert keys fail the batch.
832
+
833
+ Batch values support ordinary properties and stored `rel.one` assignments. Blob
834
+ and `rel.many` assignments are rejected; use the existing single-record methods
835
+ for those fields. Warehouse-backed and platform-owned built-in entities are not
836
+ batch write targets. `OntologyWrites` exposes the same operations with an
837
+ explicit `entity` and snake_case field names; entity handles convert authored
838
+ camelCase names and select values.
839
+
840
+ Each call accepts at most 200 records and 2 MiB of UTF-8 JSON input, including
841
+ record values, update IDs, and upsert key fields. One action may attempt at most
842
+ 500 rows across nonempty batch calls, including failed calls its handler catches.
843
+ Empty input returns zero counts and, in record mode, empty `items`, without SQL.
844
+ Every nonempty batch uses one savepoint inside the action transaction. A failure
845
+ rolls back all of that batch's records, history, replication references, and
846
+ edits; a handler may catch it and continue. A later uncaught action failure also
847
+ rolls back earlier successful batches.
848
+
849
+ Existing `ActionRejected`, `Conflict`, and `NotFound` errors may include
850
+ `batch: { indices, fields? }`: zero-based input positions and safe wire field
851
+ names. Batch calls are never automatically retried. Checkpoint a loader only
852
+ after confirmed action success; replay after a lost response is safe only when
853
+ the complete action is explicitly idempotent, including its other writes and
854
+ side effects. Generic upsert alone does not establish that guarantee.
855
+
856
+ ## Fresh metric queries in actions
857
+
858
+ Stored entity handles expose `queryMetrics({ metrics, groupBy?, where })` to
859
+ action handlers and `prepare`. It computes metrics the entity declares, with their
860
+ warehouse meaning, over the records `where` matches, in the action's transaction, so
861
+ it counts the handler's own writes:
862
+
863
+ ```ts
864
+ const totalsOf = (invoice: string) =>
865
+ Effect.gen(function* () {
866
+ const [totals] = yield* InvoiceLine.queryMetrics({
867
+ metrics: ["net", "tax"],
868
+ where: { invoiceId: invoice },
869
+ });
870
+
871
+ return totals; // { net: "1501", tax: "300" }
872
+ });
873
+ ```
874
+
875
+ Rows hold the `groupBy` fields and each metric as a decimal string or `null`;
876
+ without `groupBy` there is exactly one row. `where` is `find`'s query language and
877
+ must look records up through an index: `eq` or `in` on `id`, a unique property, a
878
+ `rel.one` key or the first field of a declared index. It aggregates every matching
879
+ record, with no row limit, so its cost follows the set the indexed condition
880
+ selects. Metric queries lock nothing: lock the record the decision depends on with
881
+ `forUpdate`. Balance
882
+ metrics, model metrics and workflow steps stay with the warehouse's `query_metrics`.
883
+ `aggregate`, the earlier name of `queryMetrics`, remains as a deprecated alias.
884
+ See [fresh metric queries inside an action](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology.md#fresh-metric-queries-inside-an-action).
885
+
886
+ ## Permissions
887
+
888
+ `me` (also `GET /api/ontology/me`) includes `authentication: { kind, client, scope }`
889
+ and `attribution`. OAuth provenance carries the registered client's ID/name and the
890
+ grant's scope; its permissions remain restricted by that scope. Attribution uses
891
+ stored names through workspace-authored `chat.attribution.template` (default `{name}`), with
892
+ `{client}` naming the registered OAuth client (empty for runtime/service callers). Client names are
893
+ registration metadata, without a claim of brand ownership. Ordinary browser/app
894
+ members, member API keys and pipeline workloads receive null attribution and cannot use that
895
+ provenance to post. No bearer token or client secret appears in `me`.
896
+
897
+ Since 0.27.3, verified agent runtime calls cannot discover or execute agent
898
+ invocations, including the legacy `agent_chat.send_message` relay and reserved
899
+ `agent_start/get/update/cancel` operations. MCP, REST, discovery/catalog exports,
900
+ OpenAPI and attachment promotion apply the same policy. OAuth, service and ordinary
901
+ member callers retain their usual permission checks when runtime authentication is
902
+ configured. This preparation does not advertise or enable exact chat posting.
903
+
904
+ A permission is `<entity>:<verb>`. The verbs are `read`, the generated `create`, `update`,
905
+ `delete`, `link` and `unlink`, and the names of authored actions. A role may use `*` for the
906
+ entity, the verb or both: `lead:*`, `*:read`, `*:*`. An entity wildcard never covers the
907
+ built-in `agent_chat`, `app_feedback` or `app_feedback_file`: reading or changing them needs a
908
+ permission that names them, such as `agent_chat:read`.
909
+
910
+ - Reads require `<entity>:read`: lists, queries, records, history, both entities of a
911
+ relationship traversal, and every entity a metric query touches. `include` and a filter through a relationship require `read` on
912
+ the related entity. A missing permission answers 403 with `<permission> is required.`
913
+ - Each action requires one permission. The generated `upsert_by_<property>` requires both
914
+ `<entity>:create` and `<entity>:update`.
915
+ - A principal's permissions are the union of its roles' permissions in the ontology.
916
+ Members hold `member` and their assigned roles; an API key holds exactly the roles it was
917
+ created with; a workflow's runs hold the workflow's role and what `member` grants. A role the
918
+ ontology no longer declares grants nothing.
919
+ - The catalog, `describe`, `graph`, `openapi.json` and `me` need only authentication;
920
+ `status`, `sign-in/check` and the `docs` page need none. `?as=me` lists only the actions the caller may run.
921
+ - `check` fails on a role without permissions, on a permission whose entity or verb does not
922
+ exist, and on `allow`, which version 0.2 replaced. It warns on an action no role may run
923
+ and on `*:*`.
924
+
925
+ ## Discover the ontology over MCP
926
+
927
+ `/mcp` uses stateless Streamable HTTP: POST returns JSON-RPC results, while authenticated
928
+ GET requests accepting `text/event-stream` receive an immediate SSE comment and 15-second
929
+ heartbeat comments. The GET stream carries no JSON-RPC messages or session ID. It closes
930
+ after 45 seconds with a reconnect hint; clients can reconnect independently to any instance.
931
+ Set `mcpStreamMaxDurationMs` in `createOntologyService` to change that lifetime, keeping it
932
+ below the deployment's function limit with room for startup and authentication. The generated
933
+ Vercel service enables Fluid and Nitro response streaming without overriding `maxDuration`;
934
+ the conservative default also fits a 60-second function limit. GET and POST share bearer
935
+ authentication and Origin rules. DELETE is unsupported because there are no sessions.
936
+
937
+ The tools carry the ontology's metadata: each `<entity>_find` says what its fields and their
938
+ values mean, its related records and its metrics; each action's tool says what it does and when
939
+ it is refused. The metric tools cover numbers: `list_metrics`, `get_dimensions`,
940
+ `get_dimension_values` and `query_metrics`, which replaces the `aggregate` tool. MCP no longer lists `describe`. `discover` is listed only by an ontology that ranks
941
+ entities by meaning with `defineOntology({ discovery: { model } })`. Stored schema visibility does
942
+ not grant permission to read records or run operations.
943
+
944
+ Optional page discovery uses the global read-only `navigation` tool. Opt into the app SDK's
945
+ `withPageCatalog` integration and add page metadata beside participating Next.js pages.
946
+ `segmentstream deploy` packages the generated catalog, and `ontologyWorkflows()` binds it
947
+ into the ontology build. The service uses that immutable bundled catalog automatically and
948
+ rejects a conflicting explicit `navigation` configuration.
949
+
950
+ For standalone services, pass `navigation: PageCatalogV1` to `createOntologyService` and
951
+ configure verified `appOrigins`. Omitting both catalogs preserves the existing tool surface; a catalog with
952
+ `pages: []` enables empty discovery. The dependency-free Node contract is exported from
953
+ `@segmentstream/ontology/navigation-catalog`: `createPageCatalog(pages, { ontologyHash,
954
+ sourceHash })` generates a deterministic revision, and `parsePageCatalog(value)` validates an
955
+ artifact. Entity references are also checked against the serving ontology.
956
+
957
+ Call `navigation({ entity?, query?, after?, limit?, expected_revision? })` to list or search page
958
+ titles and descriptions. Results contain complete metadata, a `revision`, and `next_cursor`.
959
+ Static pages have `url`; dynamic pages have `url_pattern` and their actual parameter shapes and
960
+ authored hints. Every associated entity must be visible through ordinary definition discovery.
961
+ The tool reads no records and grants no page or data access. Page size defaults to 20, is capped
962
+ at 50, and ends before the combined text and structured result exceeds 32 KiB. Cursors bind the
963
+ revision, filters, ordering, metadata visibility, and verified origin. Missing or invalid enabled
964
+ catalogs and unverified app origins fail explicitly instead of returning a misleading empty list.
965
+
966
+ Every MCP client receives the permitted operation tools directly through standard `tools/list`.
967
+ Large external catalogs are paginated: follow `nextCursor` by sending it as `params.cursor` until
968
+ it is absent. Each page contains at most 128 tools and normally fits within 500,000 UTF-8 bytes;
969
+ an individually larger tool gets its own page. Descriptions, input fields, annotations and trusted
970
+ metadata are preserved. Unicode property regex constraints (`\p{…}` and `\P{…}`) are omitted
971
+ from advertised input schemas because MCP clients use different regex engines. The original
972
+ action schema still enforces every constraint on each call. Optional output schemas are omitted.
973
+
974
+ The server no longer exposes `ontology_search_tools`, `ontology_describe_tool`, or
975
+ `ontology_call_tool`. Clients own tool search and call the selected original name with
976
+ `tools/call`. Refresh connector catalogs after upgrading, and remove `mcp.toolListing` from
977
+ workspace configuration. `discover`, where listed, ranks domain entities by meaning; it is not a
978
+ substitute for MCP tool discovery.
979
+
980
+ Each page rechecks permissions. Cursors belong to the current catalog and permission scope;
981
+ restart listing if either changes. Verified in-app runtimes receive the same direct operations
982
+ in one page because Eve 0.85 currently reads only the first `listTools` response. This preserves
983
+ running agents during the upgrade without exposing the retired helpers to either caller type.
984
+
985
+ `ontology://catalog` remains an explicit full manifest export; REST descriptions and CLI catalog
986
+ rendering retain whole-catalog behavior. Custom prompts that call `describe` should use the tools'
987
+ own descriptions, or request the full export.
988
+
989
+ ## Serve the ontology
990
+
991
+ List and query endpoints accept `select` to avoid fetching unused base fields:
992
+ `GET /api/ontology/lead?select=email,status` or a query body with
993
+ `{ "select": ["email", "status"] }`. An explicit empty list returns ids only.
994
+ Selected pages include `id` and `projected: true`; omitted fields are absent.
995
+ `include` still adds complete related records under the same read permissions.
996
+ Filters, scopes and cursor ordering apply before projection and may use fields
997
+ not selected. Without `select`, the complete record response is unchanged.
998
+ Authored action reads (`Entity.find`, `get`) remain complete records.
999
+
1000
+ ```ts
1001
+ import { configFromEnv, createOntologyService } from "@segmentstream/ontology";
1002
+
1003
+ import ontology from "./index";
1004
+
1005
+ const service = createOntologyService(ontology, configFromEnv());
1006
+
1007
+ // Give every request under /api/ontology to the service.
1008
+ export function handleOntologyRequest(request: Request): Promise<Response> {
1009
+ return service.handler(request);
1010
+ }
1011
+ ```
1012
+
1013
+ `GET /api/ontology/docs` serves a Scalar API reference over `openapi.json`. A browser signed
1014
+ in to the app authenticates with the app's Neon Auth session cookie
1015
+ (`__Secure-neon-auth.session_token`), which the service exchanges at Neon Auth for the
1016
+ session's token and verifies like a bearer token. A bearer token always takes precedence. The
1017
+ cookie authenticates a `POST` only when its `Origin` is the app's own.
1018
+
1019
+ `handler(request)` takes a web `Request` and returns a `Response`. `dispose()` closes the
1020
+ database pool. `configFromEnv()` reads these variables (`ENVIRONMENT_VARIABLES`, and the
1021
+ optional `NEON_AUTH_ENVIRONMENT_VARIABLES`). It throws `OntologyServiceConfigError` when
1022
+ one is missing or invalid, and never repeats the database URL.
1023
+
1024
+ | Variable | Purpose |
1025
+ | ------------------------------------- | ----------------------------------------------------------------------------------------------- |
1026
+ | `SEGMENTSTREAM_ONTOLOGY_DATABASE_URL` | Pooled connection string of the `ontology_app` role. |
1027
+ | `SEGMENTSTREAM_URL` | SegmentStream origin. It issues and verifies app tokens, and answers metric queries over https. |
1028
+ | `SEGMENTSTREAM_APPLICATION_ID` | App tokens must be issued for this application. |
1029
+ | `SEGMENTSTREAM_WORKSPACE_ID` | The workspace of the app. |
1030
+ | `SEGMENTSTREAM_ORGANIZATION_ID` | The organization of the workspace. |
1031
+ | `NEON_AUTH_BASE_URL` | Optional. The workspace's Neon Auth URL; its origin issues users' tokens. |
1032
+ | `NEON_AUTH_JWKS_URL` | Optional, set with the base URL. The keys that verify those tokens. |
1033
+ | `SEGMENTSTREAM_APP_CREDENTIAL` | Optional. The workspace app credential; without it, metric queries answer 503. |
1034
+ | `SEGMENTSTREAM_RUNTIME_OIDC_ISSUER` | Optional. The team's Vercel OIDC issuer, for the app's agent. |
1035
+ | `SEGMENTSTREAM_RUNTIME_OIDC_AUDIENCE` | Optional, set with the issuer. The audience of the team's tokens. |
1036
+
1037
+ The platform sets these variables in the workspace deployment, the Neon Auth pair once the
1038
+ workspace has Neon Auth and the runtime pair on deployments with an agent. With the runtime
1039
+ pair, Vercel's `VERCEL_PROJECT_ID` must be set too.
1040
+
1041
+ ### App preview deployments
1042
+
1043
+ A preview requires the emitted `workspace-app-previews-v1` runtime capability and
1044
+ `workspace-app-preview-workflows-v1` for workflow execution. The platform also
1045
+ probes the installed SDK's fixed capability profile and expiry checks on the locked dependencies
1046
+ before app code is exposed; preview support is never inferred from an unpublished release number.
1047
+
1048
+ `configFromEnv()` reads `SEGMENTSTREAM_DEPLOYMENT_TARGET` (production when omitted),
1049
+ `SEGMENTSTREAM_ONTOLOGY_DEPLOYMENT_ID` and `SEGMENTSTREAM_PREVIEW_EXPIRES_AT`. Explicit previews
1050
+ require a deployment UUID, a valid ISO deadline and child Neon Auth bindings. The returned
1051
+ `deployment` observation and `/api/ontology/status` report the deployment identity, expiry and
1052
+ capabilities. Preview admission uses the fixed preview profile even if a custom configuration
1053
+ supplies broader capability values. SDK failures use `DeploymentAdmissionError.code`; workspace
1054
+ code owns their presentation.
1055
+
1056
+ Preview services accept child Neon tokens and email sessions, checking the exact child issuer
1057
+ even when keys were copied from production. Legacy app and production workload tokens, opaque
1058
+ API keys, OAuth/channel sessions and Cloud federation are refused. Ontology CRUD, files and
1059
+ manual workflow starts retain their normal user permission checks. Action/change triggers and
1060
+ inbound webhooks can start workflows; `ontologyWorkflows()` compiles and mounts their queue
1061
+ handler. Trusted workspace code can make external network requests and cause real external
1062
+ side effects. Managed connections, after-commit hooks, agents (including workflow chat delivery
1063
+ and agent tasks), pipelines and replication remain unavailable. Scheduled business cron triggers
1064
+ are never registered, and direct calls to their trigger routes are refused even with a cron secret.
1065
+
1066
+ Workflow execution requires the runtime `VERCEL_DEPLOYMENT_ID` system variable. New preview runs
1067
+ carry immutable `segmentstream.deploymentTarget` and `segmentstream.deploymentId` attributes;
1068
+ run reads, steps, hook resumes and decoded queue callbacks require that exact app deployment UUID
1069
+ and the Workflow SDK's matching provider deployment ID. Missing or copied production provenance
1070
+ is refused. Production cannot read or resume preview runs. A stable alias is not run ownership.
1071
+ Every admission checks the deadline, including cached sessions, warm callbacks and workflow steps.
1072
+ Work already admitted can finish; preview expiry does not isolate or cancel arbitrary workspace code.
1073
+
1074
+ Platform workflow starts, reads and recovery use short-lived `platform:<workspace id>` workload
1075
+ tokens with `environment=preview`, both deployment IDs equal to the exact child UUID, the request's
1076
+ immutable candidate origin, and a token expiry no later than the preview deadline. These tokens
1077
+ grant no other ontology operations. Recovery calls `POST /api/ontology/workflows/sweep` with that
1078
+ token to retry persisted `_workflow_starts`; preview GET sweeps and cron-secret admission are refused.
1079
+ The platform must schedule this recovery separately without registering workspace business crons.
1080
+
1081
+ Each preview gets a fresh `SEGMENTSTREAM_WEBHOOK_SECRET` for unsigned URL tokens, never the
1082
+ production value. Signed webhooks and Meta registration retain their declared validation and use
1083
+ the backend's existing verification endpoints. Only those calls carry the child app credential as
1084
+ `x-segmentstream-preview-credential` plus `x-segmentstream-ontology-deployment-id`, runtime Vercel
1085
+ OIDC, organization and request headers. The backend verifies the credential's exact live deployment
1086
+ and the deployed secret declaration; named signing secret values are never injected into the preview.
1087
+
1088
+ Before exposing a verified child database, the provisioner runs:
1089
+
1090
+ ```ts
1091
+ import { preparePreviewDatabase } from "@segmentstream/ontology";
1092
+
1093
+ yield * preparePreviewDatabase({ sql });
1094
+ ```
1095
+
1096
+ This transactional, repeatable preparation clears SDK-owned API keys, OAuth/federation
1097
+ sessions and tokens, agent pairings/identities/confirmations, workflow starts/tasks, upload
1098
+ sessions, file action replay results and replication delivery state. It disables copied
1099
+ replication while preserving users, roles, persistent member blocks, channel policy, business records, histories and retained file
1100
+ revisions. Older databases with fewer SDK tables are supported. It never changes `neon_auth`
1101
+ or any provider-owned schema: child Auth invalidation, origins, email delivery and secrets
1102
+ remain the provisioner's responsibility. Call it only on a verified, unexposed child, before new
1103
+ preview workflows can start; never repeat preparation on a serving child or recovery would lose
1104
+ that child's pending starts. Copied production starts and tasks must be cleared before exposure.
1105
+
1106
+ The preview `SEGMENTSTREAM_ONTOLOGY_STORAGE` binding additionally requires `deploymentId`
1107
+ matching this deployment and the provisioner's verified `productionBranchId`, which must differ
1108
+ from `branchId`. Endpoint validation still binds storage to that child branch. The provisioner
1109
+ must verify that DB, Auth and storage all belong to the same child and issue child-only
1110
+ credentials; SDK self-consistency checks cannot prove provider ownership. The runtime rebinds
1111
+ copied file revision metadata to that child's provider and bounds signed transfer grants by
1112
+ the preview deadline. Production storage bindings need neither new field.
1113
+
1114
+ ### Authentication
1115
+
1116
+ Every route but `status` and `sign-in/check` takes `Authorization: Bearer …`:
1117
+
1118
+ - **Neon Auth token.** The app's users sign in with the workspace's Neon Auth (Managed
1119
+ Better Auth). The service verifies their JWT: EdDSA, issued by the origin of
1120
+ `NEON_AUTH_BASE_URL`, against the keys at `NEON_AUTH_JWKS_URL`. The token names the user
1121
+ (`sub`, `email`, `name`) and carries no roles. The email counts only when Neon Auth
1122
+ verified it.
1123
+ - **SegmentStream app token.** Still accepted while workspaces move to Neon Auth. The
1124
+ service picks the verifier by the token's issuer; a token from any other issuer is a 401.
1125
+ A verified app-token email also resolves an existing Neon Auth member, without replacing
1126
+ the member's Neon identity. The service reads that member's current roles and active state.
1127
+ - **API key.** `ssk_…`, held by a service user.
1128
+ - **Workload token.** Issued by SegmentStream for production operations and the exact preview
1129
+ workflow operations described above. Its subject
1130
+ decides who it is: `pipeline-workers:<workspace id>` is the pipeline worker;
1131
+ `platform:<workspace id>` is SegmentStream acting for the console and the CLI, admitted only
1132
+ to the [workflow routes](#runtime-and-routes) and refused everywhere else. Any other subject is
1133
+ a 401; a workload token never resolves as a member or the agent.
1134
+
1135
+ Sign-in finds the user linked to the token's provider and subject, else links the member
1136
+ invited with the same email, else follows the ontology's `signIn` policy: `invited` answers
1137
+ 403 `{ "error": "not_invited", "detail": "Ask your administrator for an invitation." }`,
1138
+ `open` creates a member. Deactivated members are refused. Each sign-in sets the user's
1139
+ `last_sign_in_at`, at most once per 15 minutes, so the user's history records it.
1140
+
1141
+ Members without an email can be managed by their user UUID in the console and CLI:
1142
+ `ontology member grant|revoke|deactivate|reactivate MEMBER`. Find the UUID with
1143
+ `ontology member list`. The platform's `POST …/ontology/members/roles` accepts
1144
+ either `email` (including new invitations) or `memberId` (existing members only).
1145
+ The SDK's `updateMemberRoles(ontology, { id, grant, revoke })` uses the same role
1146
+ writer as email updates. Deactivation preserves the member's roles and identities.
1147
+
1148
+ `GET /api/ontology/me` answers who the caller is, for the app to render what they may do:
1149
+ `{ user: { id, name, email, kind, active }, roles, permissions, authentication, attribution }`. It needs only
1150
+ authentication; services get `email: null`.
1151
+
1152
+ `POST /api/ontology/sign-in/check` with `{ "email": "…" }` answers, before the app emails
1153
+ anyone, whether the address may sign in and how. Under `invited`, an active member who has
1154
+ signed in with Neon Auth gets `{ "allowed": true, "method": "code" }`, an active member who
1155
+ has not gets `method: "link"` (Neon Auth sends codes only to existing accounts; a sign-in
1156
+ link creates the account), and anyone else, deactivated members included, gets
1157
+ `{ "allowed": false }`. Under `open`, every address gets `link`. It reads nothing else and
1158
+ changes nothing. A malformed address answers 400. Each service instance allows a client
1159
+ address (`x-real-ip`, else the first `x-forwarded-for` hop) ten checks a minute, then
1160
+ answers 429 `rate_limited` with `retry-after`. `checkSignIn(ontology, email)` is the same
1161
+ lookup for code that holds a SQL client.
1162
+
1163
+ ## App feedback
1164
+
1165
+ Text feedback is enabled when `feedback` is omitted. Every authenticated human member can
1166
+ submit, read their own items and acknowledge answers, including members with no assigned roles.
1167
+ It works through the app's REST API or the customer's own OAuth MCP client; an in-app agent,
1168
+ pairing and Telegram are optional. Upgrade the ontology SDK and deploy to apply these defaults;
1169
+ running deployments and historical manifests do not change retroactively.
1170
+
1171
+ The reserved built-in `feedback_reporter` role supplies this authority during human principal
1172
+ resolution. It is never stored or assignable, cannot be declared as a workspace role, and cannot
1173
+ be used by API keys, services, workflows, hooks or `feedback.builders`. The workspace's `member`
1174
+ permissions remain unchanged. OAuth `ontology:read` consent still removes writes.
1175
+
1176
+ Files follow a conservative policy resolved without network calls:
1177
+
1178
+ | Declaration | Text | Files |
1179
+ | ------------------ | -------- | --------------------------------------------------------- |
1180
+ | omitted or `{}` | enabled | only if another non-feedback entity has a blob |
1181
+ | `true` | enabled | enabled, preserving the original explicit opt-in contract |
1182
+ | `false` | disabled | disabled |
1183
+ | `{ files: false }` | enabled | disabled, even if another entity has a blob |
1184
+ | `{ files: true }` | enabled | explicitly enabled |
1185
+
1186
+ Text-only feedback adds `app_feedback` with submit, respond and acknowledge. When files resolve
1187
+ enabled, `app_feedback_file` adds a 25 MiB blob and attach action, and `app_feedback.files` is
1188
+ available as an inverse relationship. File storage still requires the platform's entitlement,
1189
+ region and deployment verification; declaring feedback never bypasses those refusals.
1190
+ `check` and `describe` report the resolved enabled/files/builders settings. Disabling feedback or
1191
+ files follows normal migration retention and drop protections; it never silently deletes records.
1192
+
1193
+ Builders default to declared roles explicitly granting `user:grantRoles` (or `user:grant_roles`)
1194
+ or `user:*`. `*:*` does not select a builder. Selected roles gain unscoped `app_feedback:read`,
1195
+ `app_feedback:respond`, and, only when files exist, `app_feedback_file:read`. Override the selection
1196
+ with declared roles or normalized role names:
1197
+
1198
+ ```ts
1199
+ export default defineOntology({
1200
+ entities: [/* business entities */],
1201
+ roles: [admin, support],
1202
+ feedback: { builders: [admin, "support"], files: false },
1203
+ });
1204
+ ```
1205
+
1206
+ `builders: []` injects no builder grants. Authored responder grants still count. With no effective
1207
+ role able to read and answer, `check` emits `feedback-unanswerable` as a warning; configure
1208
+ `feedback: { builders: [...] }` to choose the responders. The starter has no qualifying member
1209
+ administrator and intentionally warns until its workspace chooses a builder; it invents no admin role.
1210
+
1211
+ Neither ordinary nor pipeline workload `*:*` grants feedback. Deliberately authored named service
1212
+ permissions keep their ordinary meaning, including their scopes. In particular, workflow `me`
1213
+ is the service user's ID; a named grant scoped to `reporter: me` can match its own records.
1214
+ There is no automatic reporter authority in workflows, pipeline workers or admission hooks.
1215
+
1216
+ The MCP server preserves submit in compact listing and adds feedback guidance only when submit
1217
+ is visible. Attachment guidance appears only when attach is visible. The caller offers once per
1218
+ problem, asks approval for the title and summary, then later reports and acknowledges the answer.
1219
+ Builders use `segmentstream ontology feedback list`, `show` and `respond` as their app member.
1220
+
1221
+ ## Agents
1222
+
1223
+ The app's own agent runs in the same Vercel project as the service and calls it as the
1224
+ deployment: `Authorization: Bearer <Vercel OIDC token>`. The service accepts the token when it
1225
+ is RS256 from `SEGMENTSTREAM_RUNTIME_OIDC_ISSUER` (keys at `<issuer>/.well-known/jwks`), for
1226
+ `SEGMENTSTREAM_RUNTIME_OIDC_AUDIENCE`, with `project_id` equal to `VERCEL_PROJECT_ID` and
1227
+ `environment` `production`. Without the issuer and audience such tokens are a 401.
1228
+
1229
+ The agent acts for a member by adding `X-SegmentStream-Act-As: <user id>` on any route,
1230
+ `/mcp` included: the request runs with the member's current roles and history names the
1231
+ member, as if they had called. A member who is inactive or has no live pairing is a 403.
1232
+
1233
+ Running a task a workflow handed it (see [Agent tasks](#agent-tasks)), the agent instead presents
1234
+ the task's secret in `X-SegmentStream-Agent-Task: <secret>`, on `/mcp` and the two task routes
1235
+ only. While the task is live, neither ended nor past its deadline, the request runs as the
1236
+ workflow's user, as the workflow's steps do, with the caller `{ kind: "runtime" }` and
1237
+ `authenticatedVia` `agent_task`: its tool list, permission checks and history follow that user.
1238
+ Both headers on one request, a secret that names no live task, an inactive workflow user, and the
1239
+ task header on any other route are a 403.
1240
+
1241
+ | Route | Does |
1242
+ | ----------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
1243
+ | `GET /api/ontology/agent/tasks/current` | `{ id, workflow, prompt, deadline_at, principal: { id, name, roles } }`; `roles` includes `member` |
1244
+ | `POST /api/ontology/agent/tasks/current/result` | `{ status: "completed" \| "needs_input", text, truncated }` or `{ status: "failed", reason: "agent_failed" }` → `{ accepted: true }`; 409 when the task already ended |
1245
+
1246
+ Without either header the agent may call only the agent routes:
1247
+
1248
+ | Route | Caller | Does |
1249
+ | ------------------------------------------------------------- | ------ | ---------------------------------------------------------------------------------------------------------------------------- |
1250
+ | `POST /api/ontology/agent/pairings` | agent | `{ provider, external_id, display? }` → `{ code, expires_at }` |
1251
+ | `GET /api/ontology/agent/pairings/{code}` | member | `{ provider, display, expires_at }`, for the connect page |
1252
+ | `POST /api/ontology/agent/pairings/{code}/approve` | member | Pairs the account with the member → `{ identity }` |
1253
+ | `POST /api/ontology/agent/identities/resolve` | agent | Admission and turn limits; see below |
1254
+ | `POST /api/ontology/agent/identities/notice` | agent | `{ provider, external_id, sha256, version? }` acknowledges notice delivery: 204 |
1255
+ | `GET /api/ontology/agent/identities/{provider}/{external_id}` | agent | `{ user: { id, name }, roles }`; 404 unpaired, 403 revoked, inactive |
1256
+ | `GET /api/ontology/agent/identities` | member | `{ identities }`: the member's live identities |
1257
+ | `DELETE /api/ontology/agent/identities/{id}` | member | Revokes one of them: 204 |
1258
+ | `POST /api/ontology/agent/chats/seen` | agent | `{ provider, externalId, title, memberId?, leftMemberId?, agentLeft?, migratedFrom? }` → `{ id, title, purpose, whenToUse }` |
1259
+
1260
+ A pairing code is eight characters without look-alikes (no 0, O, 1, I or L), stored as its
1261
+ SHA-256, valid ten minutes and used once. Each instance allows a client address 60 new
1262
+ pairings a minute. An account and a member each have one live identity per provider:
1263
+ approving a code replaces the member's own previous account on the same provider. An account
1264
+ already live on another member returns 409 `identity_in_use` without consuming the code;
1265
+ re-approving the same member/account returns the existing identity. To release a self-registered
1266
+ account for pairing to an email member, an administrator can hard-delete the channel-only member;
1267
+ its identities are deleted with it. This escape hatch discards that member record. Deactivation
1268
+ keeps the identity bound and cannot resolve a 409. Member routes take a Neon Auth session or token and refuse API keys and the
1269
+ agent, even acting as a member, so only members pair. Pairings and identities live in the
1270
+ runtime tables `_agent_pairings` and `_agent_identities`; the migration creates them in a
1271
+ database an earlier SDK migrated. Additive migrations also create `_agent_counters` and identity
1272
+ `origin`, `noticed_at`, `notice_sha256` metadata without changing existing bindings.
1273
+
1274
+ `POST …/identities/resolve` accepts `{ provider, external_id, chat: "private" | "group", turn,
1275
+ display?, username?, notice_version?, notice_sha256?, limits? }` with a verified runtime token.
1276
+ It returns 200 with `status`:
1277
+
1278
+ - `created` or `existing`: `{ user: { id, name }, roles, provisioned, notice_due, on_new_user_pending }`.
1279
+ The optional onboarding hook has already been attempted before this response; pending means retry.
1280
+ - `pairing_required`: unknown account on an invited channel, in a group, or with `turn: false`.
1281
+ - `refused`: `{ reason: "blocked" | "inactive" }`; revoked bindings also return `blocked` and never re-create.
1282
+ - `limited`: `{ scope, retry_after_seconds, notify, notification_id? }`, before expensive agent processing.
1283
+
1284
+ `limits` has the v2 agent shape:
1285
+ `{ perSender: { turnsPerMinute: 6, turnsPerDay: 80 }, newUsersPerHour: 30, workspaceTurnsPerDay: 3000 }`.
1286
+ These values are platform defaults **and ceilings**; trusted overrides can only lower them. Scopes are
1287
+ `turnsPerMinute`, `turnsPerDay`, `newUsersPerHour`, `workspaceTurnsPerDay`. The latest submitted effective
1288
+ limits persist in `_channel_limits`; omitted limits reuse them, or defaults before the first submission.
1289
+ Only trusted agent configuration may submit limits; browser assertions must never supply them.
1290
+ Counters are durable in `_agent_counters`. Sender counters use user ids, so adding/replacing an identity
1291
+ cannot reset them. Turn caps apply only to self-registered users with exactly `["member"]`; paired-only
1292
+ members and holders of any named role are exempt. Every new registration consumes the hourly cap.
1293
+ `turn: false` never creates or counts; groups never create. Capped registration writes roll back, with a
1294
+ separate durable refusal counter. Each cap notifies on its first rejection per effective threshold
1295
+ within the fixed window, including after configured limits change. A `notify` response leases
1296
+ `notification_id` for 30 seconds. After sending app-owned limit text, POST
1297
+ `{ notification_id, delivered: true }` to `…/identities/limit-notice`; after a failed send, complete it
1298
+ with `delivered: false` so delivery can retry. A lost process's lease expires automatically. Only the
1299
+ current claim can complete; successful delivery suppresses later notifications for that threshold
1300
+ and window. No notification acknowledgement is needed when no text was sent.
1301
+ The agent supplies app-owned policy from `firstContact` and `limits`; there is no platform default
1302
+ notice. When no notice is configured, send and acknowledge nothing. Notice delivery is at least once:
1303
+ when `notice_due`, send before processing,
1304
+ then POST `{provider, external_id, version, sha256}`. Resolve carries the selected version and content
1305
+ hash; a changed version/hash requests delivery again. Unversioned acknowledgements persist the
1306
+ delivered hash too, so hash-only renewal stops requesting delivery after acknowledgement. Delivery
1307
+ is not consent. GET lookup remains compatible with old agents.
1308
+
1309
+ Server adapters share `resolveChannelIdentity(ontology, sql, input)` (camelCase `externalId`,
1310
+ `noticeVersion`, `noticeSha256`) and `sqlAgentStore(ontology, sql).resolveIdentity(input)`.
1311
+ Admission must run outside any SQL transaction (enforced), so the user commits before onboarding.
1312
+ A verified Mini App exchange calls `resolveChannelSignIn(ontology, sql, {provider, externalId, display?,
1313
+ username?})` before issuing a session. This separate verified sign-in context has no bot-turn fields or
1314
+ limit overrides: it consumes the same registration/sender/workspace caps and runs the same onboarding.
1315
+ Agent lookup-only events still use `turn: false` and never create. Browser routes must derive provider
1316
+ facts from a verified assertion, never from untrusted identity fields. Session issuance must lock the
1317
+ User row (`FOR SHARE`) and re-check active/unblocked membership in its transaction against concurrent
1318
+ block/deactivation. Do not expose trusted runtime inputs in a browser route. `runNewUserHook(ontology, sql,
1319
+ principal)` is the same lifecycle step, returning a boolean (`true` = pending). It always strips named
1320
+ roles and workload authority and atomically skips completed or non-self-registered users.
1321
+
1322
+ `setMemberBlocked(ontology, {id, blocked})` is the SDK member-management operation for A3. Runtime-only
1323
+ `POST /api/ontology/agent/members/:id/block` and `/unblock` return 204, or 404 `not_found` for unknown
1324
+ members. Member summaries retain legacy statuses, exposing blocked members as inactive with `blocked: true`.
1325
+ Block and deactivation revoke all existing OAuth grants and remove OIDC sessions in the same transaction; `revokeMemberSessions(sql,
1326
+ userId)` is B's session revocation integration point. OAuth and OIDC session issuers lock the same
1327
+ User row and recheck active/unblocked membership inside the insertion transaction, so issuance
1328
+ cannot escape concurrent revocation. Unblock never restores revoked sessions. Every
1329
+ session authentication must re-check the active, unblocked member. A blocked account cannot be paired
1330
+ elsewhere, even after unpairing; pairing conflicts remain 409 `identity_in_use`.
1331
+
1332
+ ### Channel app sessions
1333
+
1334
+ Explicit service opt-in `channels: { telegram: { connection: "chat_bot", miniApp: {} } }` mounts
1335
+ `POST /api/ontology/sign-in/:provider`, `GET /api/ontology/session` and
1336
+ `POST /api/ontology/sign-in/logout`. A chat connection alone registers no exchange. Providers remain
1337
+ invited by default: existing live identities sign in; new accounts require an explicit open channel.
1338
+ App SDK framing/sign-in also needs its own `channels.telegram: true` opt-in.
1339
+
1340
+ The exchange accepts `{assertion: "<raw initData>"}`. Telegram's `ChannelAppProvider` verifies Ed25519
1341
+ against the [pinned production key](https://core.telegram.org/bots/webapps#validating-data-for-third-party-use),
1342
+ fetches/caches getMe through the configured connection, rejects duplicates and assertions older than
1343
+ one hour or over 30 seconds ahead, and never logs assertions. An explicit test deployment may set
1344
+ `channels.telegram.miniApp.testPublicKeyHex`; production needs no key setting. Additional adapters
1345
+ implement `ChannelAppProvider.verify(assertion, ctx)` and register through `channelAppProviders`.
1346
+
1347
+ Admission/onboarding uses `resolveChannelSignIn` outside the issuer transaction with the service's
1348
+ shared action-admission gate. Issuance locks User FOR SHARE before the exact live identity and
1349
+ rechecks active/unblocked membership and ownership. The provider-derived first-party client issues
1350
+ only a one-hour `sso_` access token for the app-session resource, isolated from MCP. Discovery returns
1351
+ `session.{id,expires_at}` and `user.id`; exchange returns `{signed_in:true,expires_at}`. Browser input
1352
+ cannot override policy or limits. Refusals return codes (`not_paired`, `access_refused`,
1353
+ `invalid_assertion`, `rate_limited`), never end-user text. Public exchange has an IP budget.
1354
+
1355
+ Cookie/protocol constants live in the browser-safe `@segmentstream/ontology/channel-session`
1356
+ export shared with app SDK. The `__Host-` cookie is HttpOnly, Secure, SameSite=None, Partitioned and
1357
+ Path=/ with Max-Age=3600; deletion matches those attributes. Exchange/logout and cookie writes enforce
1358
+ the existing Origin rule. A valid channel cookie wins over OIDC/Neon; stale channel cookies fall
1359
+ through. Renewal/unpair/displacement revoke only that identity's grants in the same transaction;
1360
+ block/deactivation use A1's all-member revocation and cannot revive tokens after restoration.
1361
+
1362
+ Handlers receive read-only `principal.identities`: live `{ provider, subject, username?, verified: true }`
1363
+ facts and `createdVia` (`invite`, `email`, identity provider, or `service`) plus `authenticatedVia` for the request.
1364
+ Legacy invitations are recognized from their creation history; synthetic principals with no stored user report `unknown`.
1365
+ No credentials or trust levels are exposed. Runtime action requests may set
1366
+ `X-SegmentStream-Identity-Provider` alongside `X-SegmentStream-Act-As`; a matching live binding is required.
1367
+ Older runtimes without that header report `agent`; other verified mechanisms report their provider, `oauth`,
1368
+ `oidc`, `api_key`, or `workload`. Direct unauthenticated `runAction` calls report `unknown`.
1369
+ These facts belong to the handler context; `prepare` retains the ordinary principal contract.
1370
+
1371
+ The built-in entity `agent_chat` keeps the group chats the agent is in. The agent reports each
1372
+ message it sees there to `chats/seen`, which creates the chat or keeps its title current,
1373
+ records `lastMessageAt` at most every 15 minutes, links a paired, active writer to
1374
+ `members` and unlinks `leftMemberId`, a paired member who left, when linked, or every member
1375
+ with `agentLeft: true`, when the agent itself left (the chat stays). With `migratedFrom`, the
1376
+ old id of a group that became a supergroup, it moves the old record to `externalId`, or, when
1377
+ both are recorded, moves the members and a missing `purpose` and `whenToUse` to the new one and
1378
+ deletes the old one. `provider`, `externalId`, `title` and `lastMessageAt` are
1379
+ managed. Members set `purpose` and `whenToUse` with the generated
1380
+ `agent_chat.update`, the chat's only generated action. Chat read/update permissions remain
1381
+ `agent_chat:read` and `agent_chat:update`, granted explicitly; wildcard entity grants do not
1382
+ cover this built-in registry.
1383
+
1384
+ Since 0.28.2, `chat_post_message` posts exact text directly through the ontology service's
1385
+ `ChatDelivery`. It does not start an agent turn. Configure a declared provider connection:
1386
+
1387
+ ```ts
1388
+ const telegram = connection("telegram", { description: "Workspace Telegram bot." });
1389
+ defineOntology({
1390
+ entities,
1391
+ roles,
1392
+ connections: [telegram],
1393
+ chat: { telegram: { connection: telegram } },
1394
+ });
1395
+ ```
1396
+
1397
+ Grant `agent_chat:post_message` explicitly (`agent_chat:*` also covers it). The former
1398
+ `agent_chat:send_message` grant does not grant posting. The old send action, instruction relay,
1399
+ `AgentChannel`/`AgentMessage` exports and agent-origin configuration have been removed.
1400
+ Older ontology definitions still expose reads; posting requires the provider mapping.
1401
+
1402
+ `chat_list({ query?, chat?, after?, limit? })` returns eligible routing metadata and server-derived
1403
+ attribution. `query` filters titles by a case-insensitive literal substring before pagination.
1404
+ `chat` selects an exact ID/title. Choose an ID or exact title; ambiguous titles return candidates and never guess.
1405
+ Paired private chats have stable handles `telegram:private:<member UUID>`. Posting permission
1406
+ allows target discovery independently of history. `user_find` still requires `user:read`.
1407
+
1408
+ External OAuth/service clients call `chat_post_message({ chat, text, reply_to?, idempotency_key })`.
1409
+ Attribution comes from verified authentication and stored names. The workspace can set
1410
+ `chat.attribution: { template, separator }`; defaults are `{name}` and `\n\n`.
1411
+ The delivered plain text is `text + separator + attribution`, preserving Unicode, whitespace and
1412
+ newlines. Ordinary member/workload credentials cannot attribute a post. Oversize final text is
1413
+ refused, never truncated or split. Telegram Markdown is rendered independently from the escaped
1414
+ attribution; only a definite formatting refusal allows one plain-text retry. Unknown delivery
1415
+ outcomes are never retried. Telegram replies require the named message to exist.
1416
+
1417
+ A durable archive reservation authorizes exactly one provider attempt. Reuse the same key on
1418
+ replay; changed content/destination conflicts. An uncertain operation is never automatically
1419
+ resent. The flat MCP receipt is `{ status: "sent" | "uncertain", message_id, url, duplicate,
1420
+ history_recorded }`; private chats may have no URL. A receipt-write failure leaves
1421
+ `history_recorded: false` without sending again. An older archive lacking the claim protocol
1422
+ cannot authorize delivery. Local provider proxies alone cannot replace the production archive.
1423
+
1424
+ `chat_history({ chat, before?, limit? })` and `chat_search` read the archive with independent
1425
+ conversation authorization: a member reads the group chats they are in (the chat's `members`,
1426
+ like its attachments), `agent_chat:read` reads every group, and a private chat is read by its
1427
+ participant. A posting service cannot read member conversations. The canonical
1428
+ REST facade is `/api/ontology/chat/{list,history,search,actions/post_message}`; the post action
1429
+ uses the standard `{ input, options?: { validate_only? } }` envelope and action response.
1430
+ Validation checks eligibility and exact text without reserving or sending.
1431
+
1432
+ `chat_get` and `chat_update` use the registry implementations. Runtime calls use the agent SDK
1433
+ 0.6.3 application-owned
1434
+ `_segmentstream` protocol; the model sees only `{ chat, text, reply_to? }`. Authenticated tools/list
1435
+ advertises `segmentstream/chat-post: 1` only with a configured provider. Deploy the updated agent
1436
+ first, then the archive migration/backend, then enable ontology delivery. HQ integration is
1437
+ maintained in its own repository.
1438
+
1439
+ ### Member identity inspection
1440
+
1441
+ Platform member summaries include optional live `identities` with `provider`, `subject` and
1442
+ `username` metadata, and a `blocked` flag. Revoked bindings are omitted; identity subjects are
1443
+ stable and usernames are mutable display metadata. Older databases without channel tables
1444
+ remain readable. Use `segmentstream ontology member list --via PROVIDER` to find a member UUID.
1445
+
1446
+ ### Agent attachments
1447
+
1448
+ Attachments remain separate member-authorized capabilities. Chat posting accepts text only;
1449
+ it has no document relay fields.
1450
+
1451
+ `GET /api/ontology/agent/files/:entity/:id[/:property]` returns `{ file, download }` for the
1452
+ current file property of an ontology record. Like temporary attachment routes, it requires a
1453
+ production runtime bearer token, `X-SegmentStream-Act-As` naming an active paired member,
1454
+ and the agent conversation/session headers. It checks `<entity>:read` before returning metadata
1455
+ and rechecks live permissions when granting a download pinned to that revision. The response is
1456
+ uncached; the download grant is short lived. Without a property the entity must have exactly one
1457
+ file property. Missing records/files return `not_found`, missing read access `forbidden`, and
1458
+ non-file or ambiguous properties `unsupported`.
1459
+
1460
+ Outbound Telegram uploads currently have an effective **2 MiB multipart request limit** in the
1461
+ connection proxy, including overhead, even though Telegram accepts larger files. The agent splits
1462
+ compatible albums to fit and returns per-file errors. Group purpose and document restrictions
1463
+ still apply; a member's read access alone does not authorize sharing a restricted document in a group.
1464
+
1465
+ A file a member sends the agent is a temporary attachment: platform metadata in the runtime
1466
+ table `_agent_attachments`, never an ontology record, with its bytes stored once as a revision in
1467
+ the ontology's file storage, beside uploads. It belongs to the member, the chat it arrived in and
1468
+ the agent session that first reads it, holds its SHA-256, size, type and name, and expires 24
1469
+ hours after receipt (`received` → `ready` → `promoted`, `discarded` or `expired`). Files are up
1470
+ to 20 MB, what Telegram lets a bot download; a member holds at most 50 live attachments. The
1471
+ table exists on deployments with file storage; the migration adds it beside the file tables, also
1472
+ to a database an earlier SDK migrated.
1473
+
1474
+ Only the agent calls these routes, with its runtime token, `X-SegmentStream-Act-As`, the chat in
1475
+ `X-SegmentStream-Agent-Conversation` (`telegram:<chat id>`) and, inside a turn, the session in
1476
+ `X-SegmentStream-Agent-Session`. Another member's attachment, another chat's or another
1477
+ session's answers 404; an expired or discarded one answers 409; a deactivated member's 403.
1478
+
1479
+ | Route | Does |
1480
+ | ---------------------------------------------------- | ------------------------------------------------------------------------------------------------------- |
1481
+ | `POST /api/ontology/agent/attachments` | `{ source, source_ref, filename, content_type, size, sha256 }` → `{ attachment, upload? }`, a PUT grant |
1482
+ | `POST /api/ontology/agent/attachments/{id}/complete` | Verifies the stored bytes' size and SHA-256 → `{ attachment }` in `ready` |
1483
+ | `GET /api/ontology/agent/attachments` | `{ attachments }`: the caller's readable attachments in this chat and session |
1484
+ | `GET /api/ontology/agent/attachments/{id}` | `{ attachment, download }`: a one-minute GET grant |
1485
+ | `POST /api/ontology/agent/attachments/{id}/promote` | `{ entity, action, input?, field?, record_id? }` → `{ attachment, deduplicated, result }` |
1486
+ | `POST /api/ontology/agent/attachments/{id}/discard` | Revokes it at once → `{ attachment }` |
1487
+
1488
+ Promotion keeps a file by running an action that takes an upload, such as an authored
1489
+ `document.upload` with `uploads.for(Document, "content")`, as the member and with their
1490
+ permissions. The service mints a completed upload ticket on the attachment's existing revision,
1491
+ fills the action's upload field (and `sha256`, when the input has one and the caller left it
1492
+ out), and runs the action with an idempotency key derived from the attachment, action and
1493
+ input, and the ticket's create intent; the action consumes the ticket as it would a browser
1494
+ upload, without copying the bytes. The target property's `accept` and `maxBytes` apply. A file
1495
+ whose SHA-256 an earlier attachment already promoted through the same action answers that
1496
+ promotion with `deduplicated: true` instead of a second record. Storage maintenance expires
1497
+ attachments past their deadline and removes the files no record adopted.
1498
+
1499
+ An MCP action tool whose input takes an upload also accepts `_options: { idempotency_key,
1500
+ create_intent }`, the options the REST route takes beside `input`. An MCP client stores a file
1501
+ in three calls and sends the bytes over HTTP, never in a tool argument. `files_upload` takes
1502
+ `entity`, `action`, `filename`, `content_type` and the exact `size`, plus `property` when the
1503
+ action takes several uploads of the entity, `record_id` when it changes an existing record and
1504
+ `idempotency_key` to resume an attempt. It returns `upload`, a short-lived `transfer` grant and
1505
+ `options`. The client sends the bytes to `transfer.url` with `transfer.method` and exactly
1506
+ `transfer.headers`, calls `files_upload_complete` with `upload.id` for `upload.ticket`, then
1507
+ calls the action tool with the ticket in its upload field and `_options` set to `options`. Both
1508
+ tools run the REST upload sessions above, with the same checks, limits and permissions. A
1509
+ failure of the storage provider answers `storage_unavailable`, and a request made while a
1510
+ deployment migrates the catalog answers `ontology_updating`; both are 503s with
1511
+ `retryable: true`, so the client repeats the same call, ideally with its own `idempotency_key`.
1512
+ While a failed attempt still holds an upload, a retry answers 409 `conflict` with
1513
+ `retryable: true` and `retry_after` seconds; a conflict without `retryable` cannot resume.
1514
+
1515
+ A caller holds at most eight open uploads. `GET /api/ontology/uploads` and the MCP tool
1516
+ `files_upload_list` discover the caller's sessions occupying those slots, with `pending`,
1517
+ `max`, IDs, filenames, target actions, expiry and `awaiting_action`. The list is bounded by
1518
+ the eight-slot limit and excludes expired sessions and earlier branches or manifests.
1519
+ It never lists another principal's sessions or returns storage credentials. `ready` means
1520
+ the transfer finished but a saving action is still required. `files_upload_get` (or
1521
+ `GET /api/ontology/uploads/:id`) reads that session and its ready ticket. Keep the original
1522
+ action input and idempotency options for retry; listing does not recreate them.
1523
+
1524
+ An action that commits settles each ticket it was
1525
+ given: one it stored is consumed, and one it did not keep, for example because it found the same
1526
+ file already stored, is released at once and storage maintenance removes its bytes. A
1527
+ rejected action leaves the ticket ready for a retry, and a retry of the committed action replays
1528
+ its saved outcome. `files_upload_cancel` with `id` cancels an open upload no action will take.
1529
+ When the limit refuses a new upload, its `pending_uploads` error lists the open `uploads` (`id`,
1530
+ `filename`, `expires_at`), so the client stores or cancels them; an untaken ticket otherwise
1531
+ expires after 24 hours.
1532
+
1533
+ ## The `segmentstream-ontology` binary
1534
+
1535
+ Run the binary in the `ontology/` folder. The entry defaults to `./index.ts`. The binary
1536
+ loads it with tsx 4.21.0, as the deploy pipeline does.
1537
+
1538
+ ```sh
1539
+ npx segmentstream-ontology check [entry] # diagnostics and a catalog summary
1540
+ npx segmentstream-ontology check [entry] --manifest # the manifest JSON
1541
+ npx segmentstream-ontology check [entry] --catalog # the catalog as prose
1542
+ npx segmentstream-ontology migrate [entry] --plan # the migration plan and its SQL
1543
+ npx segmentstream-ontology migrate [entry] # plan and apply the migration
1544
+ ```
1545
+
1546
+ - `check` prints errors and warnings on stderr with their paths. It fails on any error.
1547
+ - `migrate` checks the ontology, reads the catalog recorded in the database, plans the
1548
+ migration and applies it with the planner and applier the deploy pipeline uses. Use
1549
+ the owner connection string (`ontology_owner`, direct connection) of a Neon development
1550
+ branch. Set `SEGMENTSTREAM_ONTOLOGY_MIGRATE_URL`, or pass `--database-url <url>`. The
1551
+ binary does not read `SEGMENTSTREAM_ONTOLOGY_DATABASE_URL` and never prints the
1552
+ connection string.
1553
+ With `SEGMENTSTREAM_WORKSPACE_ID` set, it also reconciles the workflow users, as deploy
1554
+ does, including when the plan has nothing to apply.
1555
+ - Removing entities, properties or relationships updates the ontology while retaining their
1556
+ database tables, columns, enum types, records and history. Removed fields become nullable
1557
+ and lose their defaults and indexes; removed relationships lose their foreign keys, so they
1558
+ cannot constrain active writes or cascade-delete retained data. The catalog records the kept
1559
+ storage: declaring the entity again with its `id`, or the field again under its name,
1560
+ restores it with its records, constraints and indexes. Another `id` under a kept name, a kept
1561
+ field with another type, or a name the database holds without a record is refused.
1562
+ `planMigration` takes the record from `readRetainedStorage` and the schema's names from
1563
+ `readStorageNames`; `migrate` and the binary read both.
1564
+ - Removing a select option validates every retained record in the migration transaction. If any
1565
+ record still holds a removed value, the migration fails and rolls back schema and catalog changes.
1566
+ Other stored values and history are preserved; destructive storage drops stay disabled by default.
1567
+ - The planner refuses changes it cannot make safely, for example a property type change.
1568
+ A refusal prints its code and path, and nothing is applied.
1569
+
1570
+ Exit codes: `0` success, `1` errors or a failed migration, `2` refused migration, `64`
1571
+ usage error.
1572
+
1573
+ ## Deploy
1574
+
1575
+ `segmentstream deploy` uploads `ontology/` with the app. The deploy pipeline evaluates
1576
+ `ontology/index.ts`, migrates the workspace database and deploys the service under
1577
+ `/api/ontology`. A failure stops the deployment and keeps the running version.
1578
+
1579
+ ## Development tests
1580
+
1581
+ `pnpm --filter @segmentstream/ontology test` runs the ordinary suite with
1582
+ embedded PGlite. Authentication contention tests require separate PostgreSQL
1583
+ backends and run against a local disposable PostgreSQL server:
1584
+
1585
+ ```sh
1586
+ ONTOLOGY_TEST_ADMIN_URL=postgresql://postgres@127.0.0.1:5432/postgres \
1587
+ pnpm --filter @segmentstream/ontology run test:postgres
1588
+ ```
1589
+
1590
+ The test fixture accepts only a localhost `postgres` admin connection to the
1591
+ `postgres` database. It creates a randomly named test database, applies the example
1592
+ ontology, and drops that database on completion. Repository CI and the SDK release
1593
+ gate run this suite with PostgreSQL 17.
1594
+
1595
+ ## Request timing
1596
+
1597
+ Every service response includes `Server-Timing: ontology;dur=…` and `x-request-id`.
1598
+ Structured `ontology.request` runtime logs carry that ID, a safe entity route label,
1599
+ HTTP method/status and duration in milliseconds. They exclude request URLs,
1600
+ parameters, bodies, credentials and member IDs. Duration includes authentication
1601
+ and query execution up to response headers, not streaming bodies or browser rendering.
1602
+ Read hosted logs with `segmentstream logs --service ontology` (deployment permission required).
1603
+
1604
+ Status is the HTTP status, so an MCP tool error can still have status 200. This
1605
+ does not collect browser web vitals, page rendering times, database plans or
1606
+ SQL-level timings. There is no per-instance sample store or MCP diagnostics
1607
+ reader. Retained request diagnostics are tracked in issue #520.
1608
+
1609
+ ## License
1610
+
1611
+ Apache-2.0
1612
+
1613
+ ## Entity sources and metric queries
1614
+
1615
+ Entities with a warehouse source declare mapped properties, metrics and relationships without ontology storage tables or actions. `find`, `query` and `get` read observations through the workspace's warehouse API, with opaque source-key record IDs and bounded, permission-checked relationship reads. See [warehouse record reads](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-record-reads.md). Stored entities may declare metrics too; they are computed over the entity's replication view, whose columns are the properties' snake_case names. See [ontology metrics](https://github.com/segmentstream/segmentstream/blob/main/docs/ontology-metrics.md) for declaration rules, metric query paths, source verification and workspace authorization.
1616
+
1617
+ See [release order and rollback](../../docs/agent-chat-delivery-migration.md).
1618
+
1619
+ ### Telegram link previews
1620
+
1621
+ Telegram delivery defaults to `external`: preview the first external HTTP(S) destination
1622
+ and suppress previews when all links belong to the workspace. Hosted deployments use
1623
+ `agent/segmentstream.json` channel settings. Standalone services accept
1624
+ `channels: { telegram: { connection: "bot", linkPreviews: "external", additionalOwnOrigins: [] } }`
1625
+ and `appOrigins: ["https://app.example"]` in their service configuration. Opt into `none`
1626
+ to suppress every preview or `all` for Telegram's native first-link preview. Without known
1627
+ workspace origins, `external` retains native behavior. Settings apply only to the named
1628
+ destination connection and do not enable a provider. A different destination uses the
1629
+ platform `external` default, independently of source overrides. Precedence is explicit modern
1630
+ provider options, then legacy options, then channel settings, then the `external` default.
1631
+ The public `@segmentstream/ontology/telegram` entry exports the pure preview
1632
+ policy, existing-parser link extraction and runtime metadata helpers for other senders.
1633
+
1634
+ ### Opt-in after-commit actions
1635
+
1636
+ Declare `defineOntology({ hooks: { role: "hook" }, roles: [hook], ... })` to expose
1637
+ `event.run({ entity, action, input, as: "service" })` to configured after-commit hooks. The role
1638
+ must exist. Each call runs through the normal action pipeline as a stable keyless service with
1639
+ only that role's current permissions, without member baseline grants or the originating member's
1640
+ rights. Deactivated service actors and revoked role assignments stay revoked. Without the
1641
+ `hooks` declaration the event has no `run`. Hooks execute after commit; failures are logged and
1642
+ cannot undo the originating action. Workspaces own recursion prevention, delivery attempts and
1643
+ any retry policy.
1644
+
1645
+ Authenticated runtime actions can read `context.runtime?.source`. It is populated only from
1646
+ verified runtime metadata, including Telegram source and staged attachment bindings, and is
1647
+ absent for ordinary OAuth/API-key input. Do not trust an action input property named `source`.
1648
+
1649
+ ### Replication lifecycle and freshness
1650
+
1651
+ Cloud publishes a generation's views after its baseline, backfill and replay preparation finish
1652
+ and Pub/Sub accepts its fixed initialization prefix. Later writes do not postpone that boundary
1653
+ or make published views unavailable. Pub/Sub acceptance does not prove BigQuery visibility;
1654
+ the native BigQuery subscription handles delivery and retries. Replication status reports
1655
+ `captured` and `accepted`, and metric query responses carry no destination freshness watermark.
1656
+ Initial copies and replay remain unavailable until acceptance and view publication finish.