spine-framework 0.4.4 → 0.5.0-rc.5

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