spine-framework 0.4.3 → 0.5.0-rc.4

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 (320) hide show
  1. package/.env.example +13 -3
  2. package/.framework/cli/commands/bootstrap.ts +392 -0
  3. package/.framework/cli/commands/dev.ts +91 -218
  4. package/.framework/cli/commands/doctor.ts +132 -26
  5. package/.framework/cli/commands/generate.ts +38 -298
  6. package/.framework/cli/commands/init.ts +20 -1
  7. package/.framework/cli/commands/migrate.ts +36 -30
  8. package/.framework/cli/commands/migrations.ts +16 -56
  9. package/.framework/cli/commands/upgrade.ts +35 -0
  10. package/.framework/cli/commands/validate-schema.ts +147 -0
  11. package/.framework/cli/context.ts +223 -110
  12. package/.framework/cli/index.ts +6 -0
  13. package/.framework/cli/lib/project-files.ts +241 -1
  14. package/.framework/functions/_shared/agent-runner.ts +1000 -81
  15. package/.framework/functions/_shared/cil.ts +2 -1
  16. package/.framework/functions/_shared/db.ts +3 -6
  17. package/.framework/functions/_shared/hash-api-key.ts +183 -79
  18. package/.framework/functions/_shared/kb.ts +61 -7
  19. package/.framework/functions/_shared/logging.ts +562 -224
  20. package/.framework/functions/_shared/message-sequence.ts +53 -0
  21. package/.framework/functions/_shared/middleware.ts +47 -44
  22. package/.framework/functions/_shared/pipeline-runner.ts +150 -37
  23. package/.framework/functions/_shared/principal.ts +9 -6
  24. package/.framework/functions/_shared/trigger-engine.ts +149 -40
  25. package/.framework/functions/admin-data.ts +48 -31
  26. package/.framework/functions/ai-agents.ts +103 -33
  27. package/.framework/functions/api-keys.ts +301 -122
  28. package/.framework/functions/apps.ts +43 -26
  29. package/.framework/functions/cil-ingest.ts +48 -15
  30. package/.framework/functions/debug-auth.ts +14 -8
  31. package/.framework/functions/deliveries.ts +39 -12
  32. package/.framework/functions/embeddings.ts +145 -76
  33. package/.framework/functions/integration-outbound.ts +67 -27
  34. package/.framework/functions/integrations.ts +254 -127
  35. package/.framework/functions/logs.ts +273 -133
  36. package/.framework/functions/observability.ts +277 -147
  37. package/.framework/functions/pipeline-executions.ts +1 -1
  38. package/.framework/functions/pipeline-runner-background.ts +120 -0
  39. package/.framework/functions/pipelines.ts +14 -9
  40. package/.framework/functions/prompt-configs.ts +7 -4
  41. package/.framework/functions/roles.ts +8 -5
  42. package/.framework/functions/security.ts +30 -17
  43. package/.framework/functions/system-cron.ts +250 -128
  44. package/.framework/functions/tests.ts +7 -6
  45. package/.framework/functions/timers.ts +310 -107
  46. package/.framework/functions/trigger-scheduler.ts +65 -5
  47. package/.framework/functions/triggers.ts +28 -13
  48. package/.framework/functions/types.ts +13 -10
  49. package/.framework/migrations/064_episode_leases.sql +196 -0
  50. package/.framework/migrations/065_execution_tables_updated_at.sql +20 -0
  51. package/.framework/migrations/066_episode_steal_thread_merge.sql +148 -0
  52. package/.framework/migrations/067_episode_lease_stale_steal.sql +93 -0
  53. package/.framework/migrations/068_cron_schedule_timer_rpcs.sql +251 -0
  54. package/.framework/migrations/069_log_stats_window.sql +54 -0
  55. package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
  56. package/.framework/migrations/071_timer_interval_keys.sql +96 -0
  57. package/.framework/migrations/072_slug_autofill.sql +77 -0
  58. package/.framework/migrations/073_seed_member_roles.sql +26 -0
  59. package/.framework/schemas/spine.config.schema.json +62 -0
  60. package/.framework/src/App.tsx +3 -1
  61. package/.framework/src/apps/admin/index.tsx +1141 -775
  62. package/.framework/src/components/ErrorBoundary.tsx +136 -0
  63. package/.framework/src/components/admin/AdminListPage.tsx +81 -73
  64. package/.framework/src/components/admin/AdminSidebar.tsx +92 -81
  65. package/.framework/src/components/admin/EntitySelect.tsx +181 -0
  66. package/.framework/src/components/admin/PageInstructions.tsx +86 -127
  67. package/.framework/src/components/app-shell/GenericAppShell.tsx +3 -1
  68. package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
  69. package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
  70. package/.framework/src/components/auth/TurnstileWidget.tsx +9 -6
  71. package/.framework/src/components/kb/KBArticleEditor.tsx +49 -6
  72. package/.framework/src/components/layout/Layout.tsx +4 -2
  73. package/.framework/src/components/layout/Sidebar.tsx +304 -179
  74. package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
  75. package/.framework/src/components/runtime/DataHeader.tsx +96 -48
  76. package/.framework/src/components/runtime/DataListPage.tsx +3 -26
  77. package/.framework/src/components/runtime/DataStats.tsx +10 -10
  78. package/.framework/src/components/runtime/DataTable.tsx +12 -10
  79. package/.framework/src/components/ui/sheet.tsx +24 -26
  80. package/.framework/src/components/ui/textarea.tsx +74 -48
  81. package/.framework/src/hooks/useBranding.ts +47 -0
  82. package/.framework/src/hooks/useEntityList.ts +12 -9
  83. package/.framework/src/hooks/useListSchema.ts +108 -58
  84. package/.framework/src/instructions/README.md +57 -0
  85. package/.framework/src/instructions/ai-agents.md +69 -0
  86. package/.framework/src/instructions/alerts.md +80 -0
  87. package/.framework/src/instructions/api-keys.md +87 -0
  88. package/.framework/src/instructions/apps.md +77 -0
  89. package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
  90. package/.framework/src/instructions/concepts/apps-and-renderers.md +162 -0
  91. package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
  92. package/.framework/src/instructions/concepts/automation.md +186 -0
  93. package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
  94. package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
  95. package/.framework/src/instructions/concepts/design-schema.md +152 -0
  96. package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
  97. package/.framework/src/instructions/concepts/observability.md +116 -0
  98. package/.framework/src/instructions/concepts/permissions.md +150 -0
  99. package/.framework/src/instructions/concepts/runtime-records.md +111 -0
  100. package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
  101. package/.framework/src/instructions/concepts/spine-way.md +172 -0
  102. package/.framework/src/instructions/deliveries.md +59 -0
  103. package/.framework/src/instructions/guide/00-orientation.md +106 -0
  104. package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
  105. package/.framework/src/instructions/guide/02-project-map.md +147 -0
  106. package/.framework/src/instructions/guide/03-domain-model.md +223 -0
  107. package/.framework/src/instructions/guide/04-first-app.md +227 -0
  108. package/.framework/src/instructions/guide/05-glossary.md +183 -0
  109. package/.framework/src/instructions/integrations.md +96 -0
  110. package/.framework/src/instructions/internal/cil-markers.md +83 -0
  111. package/.framework/src/instructions/invites.md +60 -0
  112. package/.framework/src/instructions/kb.md +80 -0
  113. package/.framework/src/instructions/logs.md +77 -0
  114. package/.framework/src/instructions/observability.md +61 -0
  115. package/.framework/src/instructions/operations/deployment.md +115 -0
  116. package/.framework/src/instructions/operations/migrations.md +125 -0
  117. package/.framework/src/instructions/operations/security-checklist.md +148 -0
  118. package/.framework/src/instructions/operations/troubleshooting.md +170 -0
  119. package/.framework/src/instructions/operations/upgrade.md +116 -0
  120. package/.framework/src/instructions/pipeline-executions.md +62 -0
  121. package/.framework/src/instructions/pipelines.md +78 -0
  122. package/.framework/src/instructions/prompts.md +95 -0
  123. package/.framework/src/instructions/reference/api/admin-data.md +162 -0
  124. package/.framework/src/instructions/reference/api/auth.md +113 -0
  125. package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
  126. package/.framework/src/instructions/reference/app-manifest.md +150 -0
  127. package/.framework/src/instructions/reference/cli.md +124 -0
  128. package/.framework/src/instructions/reference/database.md +132 -0
  129. package/.framework/src/instructions/reference/design-schema.md +280 -0
  130. package/.framework/src/instructions/reference/env-vars.md +133 -0
  131. package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
  132. package/.framework/src/instructions/reference/functions/admin-data.md +195 -0
  133. package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
  134. package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
  135. package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
  136. package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
  137. package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
  138. package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
  139. package/.framework/src/instructions/reference/functions/apps.md +140 -0
  140. package/.framework/src/instructions/reference/functions/auth.md +99 -0
  141. package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
  142. package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
  143. package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
  144. package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
  145. package/.framework/src/instructions/reference/functions/index.md +103 -0
  146. package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
  147. package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
  148. package/.framework/src/instructions/reference/functions/integrations.md +130 -0
  149. package/.framework/src/instructions/reference/functions/invites.md +182 -0
  150. package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
  151. package/.framework/src/instructions/reference/functions/kb.md +166 -0
  152. package/.framework/src/instructions/reference/functions/logs.md +141 -0
  153. package/.framework/src/instructions/reference/functions/observability.md +105 -0
  154. package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
  155. package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
  156. package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
  157. package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
  158. package/.framework/src/instructions/reference/functions/roles.md +121 -0
  159. package/.framework/src/instructions/reference/functions/security.md +140 -0
  160. package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
  161. package/.framework/src/instructions/reference/functions/system.md +106 -0
  162. package/.framework/src/instructions/reference/functions/tests.md +97 -0
  163. package/.framework/src/instructions/reference/functions/timers.md +117 -0
  164. package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
  165. package/.framework/src/instructions/reference/functions/triggers.md +140 -0
  166. package/.framework/src/instructions/reference/functions/types.md +146 -0
  167. package/.framework/src/instructions/reference/seeds.md +126 -0
  168. package/.framework/src/instructions/reference/shared-modules.md +127 -0
  169. package/.framework/src/instructions/reference/spine-config.md +114 -0
  170. package/.framework/src/instructions/roles.md +80 -0
  171. package/.framework/src/instructions/runtime-detail.md +70 -0
  172. package/.framework/src/instructions/runtime-list.md +77 -0
  173. package/.framework/src/instructions/security.md +67 -0
  174. package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
  175. package/.framework/src/instructions/tasks/add-a-role.md +96 -0
  176. package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
  177. package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
  178. package/.framework/src/instructions/tasks/add-a-type.md +151 -0
  179. package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
  180. package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
  181. package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
  182. package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
  183. package/.framework/src/instructions/tasks/package-and-install-an-app.md +127 -0
  184. package/.framework/src/instructions/tasks/test-your-code.md +108 -0
  185. package/.framework/src/instructions/tasks/write-a-function.md +132 -0
  186. package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
  187. package/.framework/src/instructions/testing.md +59 -0
  188. package/.framework/src/instructions/timers.md +82 -0
  189. package/.framework/src/instructions/triggers.md +69 -0
  190. package/.framework/src/instructions/types.md +93 -0
  191. package/.framework/src/lib/branding.ts +128 -0
  192. package/.framework/src/pages/NoAppPage.tsx +3 -1
  193. package/.framework/src/pages/NotFoundPage.tsx +62 -31
  194. package/.framework/src/pages/SetupPage.tsx +5 -2
  195. package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
  196. package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
  197. package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
  198. package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
  199. package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
  200. package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
  201. package/.framework/src/pages/admin/AppsPage.tsx +104 -66
  202. package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
  203. package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
  204. package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
  205. package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
  206. package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
  207. package/.framework/src/pages/admin/LogsPage.tsx +144 -76
  208. package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
  209. package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
  210. package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
  211. package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
  212. package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
  213. package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
  214. package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
  215. package/.framework/src/pages/admin/RolesPage.tsx +86 -43
  216. package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
  217. package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
  218. package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
  219. package/.framework/src/pages/admin/TimersPage.tsx +90 -62
  220. package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
  221. package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
  222. package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
  223. package/.framework/src/pages/admin/TypesPage.tsx +90 -48
  224. package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
  225. package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
  226. package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
  227. package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
  228. package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
  229. package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
  230. package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
  231. package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
  232. package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
  233. package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
  234. package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
  235. package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
  236. package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
  237. package/.framework/src/pages/auth/AuthConfirmPage.tsx +3 -1
  238. package/.framework/src/pages/auth/ForgotPasswordPage.tsx +3 -1
  239. package/.framework/src/pages/auth/LoginPage.tsx +4 -2
  240. package/.framework/src/pages/auth/RegisterPage.tsx +3 -1
  241. package/.framework/templates/agent/AGENTS.md +150 -0
  242. package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
  243. package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
  244. package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
  245. package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
  246. package/.framework/templates/agent/skills/spine-install-app/SKILL.md +72 -0
  247. package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
  248. package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
  249. package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
  250. package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -0
  251. package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
  252. package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
  253. package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
  254. package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
  255. package/.framework/templates/project.json +5 -1
  256. package/.framework/tests/unit/account-scoping-gates.test.ts +392 -0
  257. package/.framework/tests/unit/agent-files.test.ts +355 -0
  258. package/.framework/tests/unit/agent-runner.test.ts +496 -47
  259. package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
  260. package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
  261. package/.framework/tests/unit/bootstrap.test.ts +242 -0
  262. package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
  263. package/.framework/tests/unit/debug-auth.test.ts +38 -0
  264. package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
  265. package/.framework/tests/unit/episode-lease.test.ts +695 -0
  266. package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
  267. package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
  268. package/.framework/tests/unit/kb-ingest-lease.test.ts +19 -0
  269. package/.framework/tests/unit/logging-app-id.test.ts +75 -0
  270. package/.framework/tests/unit/pipeline-runner-background.test.ts +120 -0
  271. package/.framework/tests/unit/pipeline-runner.test.ts +153 -1
  272. package/.framework/tests/unit/principal-cache.test.ts +15 -0
  273. package/.framework/tests/unit/system-context-denial.test.ts +145 -0
  274. package/.framework/tests/unit/system-cron.test.ts +240 -0
  275. package/.framework/tests/unit/timers.test.ts +159 -0
  276. package/.framework/tests/unit/trigger-engine.test.ts +278 -91
  277. package/.framework/tests/unit/trigger-scheduler.test.ts +78 -0
  278. package/README.md +125 -166
  279. package/config/tsconfig.cli.json +2 -2
  280. package/netlify.toml +5 -0
  281. package/package.json +15 -4
  282. package/templates/project/package-lock.json +2 -2
  283. package/.framework/docs/admin-routes.md +0 -76
  284. package/.framework/docs/api-docs-progress.md +0 -38
  285. package/.framework/docs/api-governance.md +0 -146
  286. package/.framework/docs/api-testing-results.md +0 -212
  287. package/.framework/docs/assembly-launch-guide.md +0 -138
  288. package/.framework/docs/audit-results.md +0 -590
  289. package/.framework/docs/db-api-inventory.md +0 -95
  290. package/.framework/docs/gap-remediation-backlog.md +0 -103
  291. package/.framework/docs/ui-adoption-verification.md +0 -111
  292. package/.framework/docs/ui-api-coverage.md +0 -84
  293. package/.framework/docs/v2-compatibility-audit.md +0 -228
  294. package/.framework/mcp/index.ts +0 -133
  295. package/.framework/mcp/load-env.ts +0 -60
  296. package/.framework/mcp/shared/app-manifest.ts +0 -275
  297. package/.framework/mcp/shared/db.ts +0 -477
  298. package/.framework/mcp/shared/kb.ts +0 -118
  299. package/.framework/mcp/tools/architect.ts +0 -431
  300. package/.framework/mcp/tools/cil/context.ts +0 -345
  301. package/.framework/mcp/tools/cil/graph.ts +0 -242
  302. package/.framework/mcp/tools/cil/ingest.ts +0 -1217
  303. package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
  304. package/.framework/mcp/tools/cil/pipeline.ts +0 -767
  305. package/.framework/mcp/tools/cil/project.ts +0 -791
  306. package/.framework/mcp/tools/cil/retrieval.ts +0 -336
  307. package/.framework/mcp/tools/cil/search-core.ts +0 -454
  308. package/.framework/mcp/tools/cil/search.ts +0 -97
  309. package/.framework/mcp/tools/cil/suggest.ts +0 -186
  310. package/.framework/mcp/tools/cil/validate.ts +0 -1369
  311. package/.framework/mcp/tools/kb/delete.ts +0 -139
  312. package/.framework/mcp/tools/kb/index.ts +0 -52
  313. package/.framework/mcp/tools/kb/ingest.ts +0 -84
  314. package/.framework/mcp/tools/kb/list-types.ts +0 -102
  315. package/.framework/mcp/tools/kb/publish.ts +0 -100
  316. package/.framework/mcp/tools/kb/reindex.ts +0 -86
  317. package/.framework/mcp/tools/kb/revoke.ts +0 -85
  318. package/.framework/mcp/tools/kb/search.ts +0 -101
  319. package/.framework/mcp/tools/kb/upsert.ts +0 -125
  320. package/.framework/mcp/tools/observability.ts +0 -510
@@ -0,0 +1,392 @@
1
+ import { describe, it, expect, vi, beforeEach } from 'vitest'
2
+
3
+ // S-007: machine principals resolve to the service-role adminDb via
4
+ // getPrincipalDb, so HTTP handlers that query on ctx.db or adminDb without an
5
+ // account filter were reachable cross-account by any API key. These tests pin
6
+ // the gates: non-system principals are scoped to ctx.accountId (or denied
7
+ // outright for platform-wide actions); system_admin and the internal
8
+ // SYSTEM_PRINCIPAL keep working.
9
+
10
+ vi.mock('../../functions/_shared/middleware.ts', () => ({
11
+ createHandler: (fn: any) => fn,
12
+ json: vi.fn(),
13
+ error: vi.fn(),
14
+ parseBody: vi.fn(),
15
+ }))
16
+
17
+ vi.mock('../../functions/_shared/db.ts', () => ({
18
+ adminDb: {
19
+ from: vi.fn(),
20
+ rpc: vi.fn(),
21
+ },
22
+ getUserDb: vi.fn(() => ({})),
23
+ }))
24
+
25
+ vi.mock('../../functions/_shared/audit.ts', () => ({
26
+ emitAudit: vi.fn().mockResolvedValue(undefined),
27
+ emitLog: vi.fn().mockResolvedValue(undefined),
28
+ emitSpan: vi.fn().mockResolvedValue(undefined),
29
+ emitActivity: vi.fn().mockResolvedValue(undefined),
30
+ }))
31
+
32
+ vi.mock('../../functions/_shared/permissions.ts', () => ({
33
+ sanitizeRecordData: vi.fn(async (_ctx: any, record: any) => record),
34
+ }))
35
+
36
+ vi.mock('../../functions/_shared/llm.ts', () => ({
37
+ callOpenAIEmbeddings: vi.fn(async (_ctx: any, inputs: string[]) => ({
38
+ data: inputs.map((_t, i) => ({ index: i, embedding: [0.1, 0.2, 0.3] })),
39
+ })),
40
+ callOpenAIChat: vi.fn(async () => ({ choices: [{ message: { content: 'summary' } }] })),
41
+ callOpenAI: vi.fn(async () => ({})),
42
+ callEmbeddingApi: vi.fn(async (inputs: string[]) => inputs.map(() => '[0.1,0.2]')),
43
+ }))
44
+
45
+ vi.mock('../../functions/_shared/logging.ts', () => ({
46
+ resolveAppUuid: vi.fn(async (ref: any) => ref ?? null),
47
+ }))
48
+
49
+ // admin-data pulls in permissions/trigger-engine — integration-outbound only
50
+ // calls get/update when record_id / update_record are set.
51
+ vi.mock('../../functions/admin-data.ts', () => ({
52
+ get: vi.fn(),
53
+ update: vi.fn(),
54
+ }))
55
+
56
+ import { adminDb } from '../../functions/_shared/db.ts'
57
+ import { cleanup as logsCleanup } from '../../functions/logs.ts'
58
+ import {
59
+ list as apiKeysList,
60
+ get as apiKeysGet,
61
+ validate as apiKeysValidate,
62
+ revoke as apiKeysRevoke,
63
+ listUsageLogs,
64
+ } from '../../functions/api-keys.ts'
65
+ import {
66
+ generate as embeddingsGenerate,
67
+ revoke as embeddingsRevoke,
68
+ approve as embeddingsApprove,
69
+ list as embeddingsList,
70
+ searchSimilar,
71
+ } from '../../functions/embeddings.ts'
72
+ import { execute as executeOutbound } from '../../functions/integration-outbound.ts'
73
+
74
+ // Chainable query-builder stub — every filter/terminal method returns the
75
+ // builder except single/maybeSingle/await, which resolve to the result.
76
+ function q(result: { data?: any; error?: any; count?: any }) {
77
+ const b: any = {}
78
+ const chainers = ['select', 'eq', 'in', 'is', 'ilike', 'filter', 'not', 'order',
79
+ 'limit', 'update', 'insert', 'upsert', 'delete', 'neq', 'or', 'contains',
80
+ 'gte', 'lte', 'lt', 'range', 'textSearch', 'or']
81
+ for (const m of chainers) b[m] = vi.fn(() => b)
82
+ b.single = vi.fn(async () => result)
83
+ b.maybeSingle = vi.fn(async () => result)
84
+ b.then = (onF: any, onR: any) => Promise.resolve(result).then(onF, onR)
85
+ return b
86
+ }
87
+
88
+ let queue: any[] = []
89
+ let built: any[] = []
90
+ let dbBuilt: any[] = []
91
+
92
+ beforeEach(() => {
93
+ queue = []
94
+ built = []
95
+ dbBuilt = []
96
+ vi.clearAllMocks()
97
+ vi.mocked(adminDb.from).mockImplementation(() => {
98
+ const b = queue.length ? queue.shift() : q({ data: null, error: null })
99
+ built.push(b)
100
+ return b
101
+ })
102
+ })
103
+
104
+ // ctx.db for non-system callers. In production a machine principal's ctx.db IS
105
+ // adminDb — the tests assert the .eq('account_id', ...) filter lands on it.
106
+ function stubDb() {
107
+ return {
108
+ from: vi.fn((table: string) => {
109
+ const b = queue.length ? queue.shift() : q({ data: null, error: null })
110
+ ;(b as any).__table = table
111
+ dbBuilt.push(b)
112
+ return b
113
+ }),
114
+ rpc: vi.fn(async () => ({ data: [], error: null })),
115
+ }
116
+ }
117
+
118
+ const scopedMachine = (accountId: string | null = 'acct-1', extra: any = {}) => ({
119
+ id: 'key-1',
120
+ type: 'machine',
121
+ accountId,
122
+ roles: [],
123
+ scopes: ['logs:read'],
124
+ isInternal: false,
125
+ machineType: 'integration',
126
+ provenance: { sourceType: 'api_key' },
127
+ ...extra,
128
+ })
129
+
130
+ const internalScopedMachine = (accountId: string | null = 'acct-1') => ({
131
+ ...scopedMachine(accountId),
132
+ isInternal: true,
133
+ })
134
+
135
+ const systemAdmin = () => ({
136
+ id: 'user-1',
137
+ type: 'human',
138
+ accountId: 'acct-1',
139
+ roles: ['system_admin'],
140
+ provenance: { sourceType: 'jwt' },
141
+ })
142
+
143
+ const systemPrincipal = () => ({
144
+ id: 'system',
145
+ type: 'machine',
146
+ accountId: null,
147
+ roles: [],
148
+ scopes: ['*:*'],
149
+ isInternal: true,
150
+ machineType: 'internal',
151
+ provenance: { sourceType: 'cron' },
152
+ })
153
+
154
+ function makeCtx(principal: any, overrides: any = {}) {
155
+ return {
156
+ requestId: 'req-1',
157
+ principal,
158
+ accountId: principal?.accountId ?? null,
159
+ db: stubDb(),
160
+ query: {},
161
+ headers: {},
162
+ ...overrides,
163
+ }
164
+ }
165
+
166
+ describe('logs cleanup gate (S-007)', () => {
167
+ it('denies a scoped machine principal with statusCode 403', async () => {
168
+ const ctx = makeCtx(scopedMachine())
169
+ await expect(logsCleanup(ctx, { days_to_keep: 30 })).rejects.toMatchObject({ statusCode: 403 })
170
+ expect(ctx.db.from).not.toHaveBeenCalled()
171
+ })
172
+
173
+ it('allows a system_admin principal', async () => {
174
+ queue.push(q({ data: [{ id: 'l1' }], error: null }))
175
+ const ctx = makeCtx(systemAdmin())
176
+ const res = await logsCleanup(ctx, { days_to_keep: 30 })
177
+ expect(res.deleted_count).toBe(1)
178
+ })
179
+
180
+ it('allows the internal SYSTEM_PRINCIPAL (no account binding)', async () => {
181
+ queue.push(q({ data: [{ id: 'l1' }], error: null }))
182
+ const ctx = makeCtx(systemPrincipal())
183
+ const res = await logsCleanup(ctx, {})
184
+ expect(res.deleted_count).toBe(1)
185
+ })
186
+ })
187
+
188
+ describe('api-keys account scoping (S-007)', () => {
189
+ it('get scopes the lookup to ctx.accountId for a machine principal', async () => {
190
+ const ctx = makeCtx(scopedMachine())
191
+ await expect(apiKeysGet({ ...ctx, query: { id: 'key-9' } }, {})).resolves.toBeDefined()
192
+ const b = dbBuilt[0]
193
+ expect(b.eq).toHaveBeenCalledWith('id', 'key-9')
194
+ expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
195
+ })
196
+
197
+ it('get does not scope for system_admin', async () => {
198
+ queue.push(q({ data: { id: 'key-9', name: 'k' }, error: null }))
199
+ await apiKeysGet({ ...makeCtx(systemAdmin()), query: { id: 'key-9' } }, {})
200
+ const b = built[0]
201
+ const accountEq = b.eq.mock.calls.filter((c: any[]) => c[0] === 'account_id')
202
+ expect(accountEq).toHaveLength(0)
203
+ })
204
+
205
+ it('get rejects a non-system principal without account context', async () => {
206
+ const ctx = makeCtx(scopedMachine(null))
207
+ await expect(apiKeysGet({ ...ctx, query: { id: 'key-9' } }, {})).rejects.toThrow('Account context required')
208
+ })
209
+
210
+ it('validate scopes the key lookup to ctx.accountId', async () => {
211
+ const ctx = makeCtx(scopedMachine())
212
+ const res = await apiKeysValidate(ctx, { key_value: 'sk_abcdef123456' })
213
+ expect(res.is_valid).toBe(false)
214
+ const b = dbBuilt[0]
215
+ expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
216
+ })
217
+
218
+ it('validate requires account context for non-system principals', async () => {
219
+ const ctx = makeCtx(scopedMachine(null))
220
+ await expect(apiKeysValidate(ctx, { key_value: 'sk_abcdef123456' })).rejects.toThrow('Account context required')
221
+ })
222
+
223
+ it('revoke scopes the update to ctx.accountId', async () => {
224
+ queue.push(q({ data: { id: 'key-9' }, error: null }))
225
+ const ctx = makeCtx(scopedMachine())
226
+ await apiKeysRevoke(ctx, { id: 'key-9' })
227
+ const b = dbBuilt[0]
228
+ expect(b.eq).toHaveBeenCalledWith('id', 'key-9')
229
+ expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
230
+ })
231
+
232
+ it('usage-logs scopes the query to ctx.accountId', async () => {
233
+ const ctx = makeCtx(scopedMachine())
234
+ await listUsageLogs(ctx, {})
235
+ const b = dbBuilt[0]
236
+ expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
237
+ })
238
+
239
+ it('list scopes the query to ctx.accountId for machine principals', async () => {
240
+ const ctx = makeCtx(scopedMachine())
241
+ await apiKeysList(ctx, {})
242
+ const b = dbBuilt[0]
243
+ expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
244
+ })
245
+ })
246
+
247
+ describe('embeddings account scoping (S-007)', () => {
248
+ const item = (accountId: string | null) => ({
249
+ id: 'item-1',
250
+ title: 'Doc',
251
+ description: 'body',
252
+ account_id: accountId,
253
+ data: {},
254
+ type: { slug: 'kb_article', design_schema: {} },
255
+ })
256
+
257
+ it('generate rejects a scoped caller on another account\u2019s item with 403', async () => {
258
+ queue.push(q({ data: item('acct-2'), error: null }))
259
+ const ctx = makeCtx(scopedMachine())
260
+ await expect(embeddingsGenerate(ctx, { item_id: 'item-1' })).rejects.toMatchObject({ statusCode: 403 })
261
+ })
262
+
263
+ it('generate writes vectors under ctx.accountId and ignores account_id_override for scoped callers', async () => {
264
+ queue.push(q({ data: item('acct-1'), error: null })) // items fetch
265
+ queue.push(q({ data: [], error: null })) // existing embeddings
266
+ const ctx = makeCtx(scopedMachine())
267
+ const res = await embeddingsGenerate(ctx, { item_id: 'item-1', account_id_override: 'acct-9' })
268
+ expect(res.embeddings_created).toBeGreaterThan(0)
269
+ const insertCalls = built.flatMap(b => b.insert?.mock.calls ?? [])
270
+ expect(insertCalls.length).toBeGreaterThan(0)
271
+ for (const [row] of insertCalls) {
272
+ expect(row.account_id).toBe('acct-1')
273
+ }
274
+ })
275
+
276
+ it('generate honors account_id_override for system_admin', async () => {
277
+ queue.push(q({ data: item('acct-1'), error: null }))
278
+ queue.push(q({ data: [], error: null }))
279
+ const ctx = makeCtx(systemAdmin())
280
+ await embeddingsGenerate(ctx, { item_id: 'item-1', account_id_override: 'acct-9' })
281
+ const insertCalls = built.flatMap(b => b.insert?.mock.calls ?? [])
282
+ expect(insertCalls.length).toBeGreaterThan(0)
283
+ for (const [row] of insertCalls) {
284
+ expect(row.account_id).toBe('acct-9')
285
+ }
286
+ })
287
+
288
+ it('revoke scopes the update to ctx.accountId', async () => {
289
+ queue.push(q({ data: [{ id: 'e1' }], error: null }))
290
+ const ctx = makeCtx(scopedMachine())
291
+ const res = await embeddingsRevoke(ctx, { document_id: 'doc-1' })
292
+ expect(res.revoked_count).toBe(1)
293
+ const b = built[0]
294
+ expect(b.eq).toHaveBeenCalledWith('document_id', 'doc-1')
295
+ expect(b.eq).toHaveBeenCalledWith('account_id', 'acct-1')
296
+ })
297
+
298
+ it('revoke does not scope for system_admin', async () => {
299
+ queue.push(q({ data: [{ id: 'e1' }], error: null }))
300
+ const ctx = makeCtx(systemAdmin())
301
+ await embeddingsRevoke(ctx, { document_id: 'doc-1' })
302
+ const accountEq = built[0].eq.mock.calls.filter((c: any[]) => c[0] === 'account_id')
303
+ expect(accountEq).toHaveLength(0)
304
+ })
305
+
306
+ it('approve scopes the update to ctx.accountId', async () => {
307
+ queue.push(q({ data: [{ id: 'e1' }], error: null }))
308
+ const ctx = makeCtx(scopedMachine())
309
+ const res = await embeddingsApprove(ctx, { document_id: 'doc-1' })
310
+ expect(res.approved_count).toBe(1)
311
+ expect(built[0].eq).toHaveBeenCalledWith('account_id', 'acct-1')
312
+ })
313
+
314
+ it('list scopes embeddings to ctx.accountId for machine principals', async () => {
315
+ const ctx = makeCtx(scopedMachine())
316
+ await embeddingsList(ctx, {})
317
+ expect(dbBuilt[0].eq).toHaveBeenCalledWith('account_id', 'acct-1')
318
+ })
319
+
320
+ it('searchSimilar forces filter_account_ids to ctx.accountId for scoped callers', async () => {
321
+ const ctx = makeCtx(scopedMachine())
322
+ await searchSimilar(ctx, {
323
+ query_embedding: [0.1],
324
+ filter_account_ids: ['acct-2'],
325
+ })
326
+ const rpcArgs = vi.mocked(ctx.db.rpc).mock.calls[0][1] as any
327
+ expect(rpcArgs.filter_account_ids).toEqual(['acct-1'])
328
+ })
329
+ })
330
+
331
+ describe('integration-outbound delivery_id account check (S-007)', () => {
332
+ const delivery = (accountId: string | null) => ({
333
+ id: 'del-1',
334
+ account_id: accountId,
335
+ direction: 'outbound',
336
+ status: 'pending',
337
+ attempt_count: 0,
338
+ max_attempts: 3,
339
+ payload: {
340
+ integration_slug: 'generic-http',
341
+ action_config: { url: 'https://hooks.example.com/in' },
342
+ },
343
+ })
344
+
345
+ it('rejects a scoped internal key replaying another account\u2019s delivery with 403', async () => {
346
+ queue.push(q({ data: delivery('acct-2'), error: null }))
347
+ const ctx = makeCtx(internalScopedMachine())
348
+ await expect(executeOutbound(ctx, { delivery_id: 'del-1' })).rejects.toMatchObject({ statusCode: 403 })
349
+ // Nothing beyond the delivery lookup should have run.
350
+ expect(vi.mocked(adminDb.from)).toHaveBeenCalledTimes(1)
351
+ })
352
+
353
+ it('rejects a non-internal scoped principal touching another account\u2019s delivery with 403', async () => {
354
+ queue.push(q({ data: delivery('acct-2'), error: null }))
355
+ const ctx = makeCtx(scopedMachine())
356
+ await expect(
357
+ executeOutbound(ctx, { delivery_id: 'del-1', integration_slug: 'generic-http', action_config: {} })
358
+ ).rejects.toMatchObject({ statusCode: 403 })
359
+ })
360
+
361
+ it('lets a scoped internal key replay a delivery in its own account', async () => {
362
+ queue.push(q({ data: delivery('acct-1'), error: null })) // delivery select
363
+ queue.push(q({ data: null, error: null })) // processing update
364
+ queue.push(q({ data: { id: 'int-1', slug: 'generic-http', credentials: {}, config: { outbound_adapter: 'adapters/generic-webhook-outbound' }, is_active: true }, error: null })) // tenant integration
365
+ queue.push(q({ data: null, error: null })) // result update
366
+
367
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200, text: async () => '{}' })
368
+ vi.stubGlobal('fetch', fetchMock)
369
+
370
+ const ctx = makeCtx(internalScopedMachine())
371
+ const res = await executeOutbound(ctx, { delivery_id: 'del-1' })
372
+ expect(res.success).toBe(true)
373
+ expect(res.is_retry).toBe(true)
374
+ vi.unstubAllGlobals()
375
+ })
376
+
377
+ it('lets the SYSTEM_PRINCIPAL replay any account\u2019s delivery (cron retry path)', async () => {
378
+ queue.push(q({ data: delivery('acct-2'), error: null })) // delivery select
379
+ queue.push(q({ data: null, error: null })) // processing update
380
+ queue.push(q({ data: { id: 'int-1', slug: 'generic-http', credentials: {}, config: { outbound_adapter: 'adapters/generic-webhook-outbound' }, is_active: true }, error: null })) // system integration
381
+ queue.push(q({ data: null, error: null })) // result update
382
+
383
+ const fetchMock = vi.fn().mockResolvedValue({ ok: true, status: 200, text: async () => '{}' })
384
+ vi.stubGlobal('fetch', fetchMock)
385
+
386
+ const ctx = makeCtx(systemPrincipal(), { accountId: null })
387
+ const res = await executeOutbound(ctx, { delivery_id: 'del-1' })
388
+ expect(res.success).toBe(true)
389
+ expect(res.is_retry).toBe(true)
390
+ vi.unstubAllGlobals()
391
+ })
392
+ })