openvibers/openvibe.ai Commits

AI vhost: POST /api/v1/decisions reaches the app (#513)

OP OpenVibers committed 14 h ago · f6087705f1 · 3 files, +8 −4

* AI vhost: POST /api/v1/decisions reaches the app

The vhost is an allow-list; Decisions (AI #512) answered nginx's 404 from outside while the app answered 401.
It now passes like the six direct operations: POST only, the ovai_apps rate limit.

* home test: the direct operations' location includes decisions

Changes

ddiff --git a/deploy/nginx/ai.openvibe.services.conf b/deploy/nginx/ai.openvibe.services.conf
iindex 6e20d03..f8219d5 100644
---- a/deploy/nginx/ai.openvibe.services.conf
++++ b/deploy/nginx/ai.openvibe.services.conf
@@@ -63,8 +63,8 @@ server {
# Metrics are for host-local scrapers only (http://127.0.0.1:4700/metrics).
location ~* ^/metrics(/|$) { return 404; }
-- # ── Developer apps (ai.app.run): the six direct operations and a run read ──
-- location ~ ^/api/v1/(chat|generate|summarize|classify|extract|embed)$ {
++ # ── Developer apps (ai.app.run): the six direct operations, Decisions (ai.run.create) and a run read ──
++ location ~ ^/api/v1/(chat|generate|summarize|classify|extract|embed|decisions)$ {
limit_req zone=ovai_apps burst=20 nodelay;
limit_req_status 429;
limit_except POST { deny all; }
ddiff --git a/test/home.test.js b/test/home.test.js
iindex e53f0e3..5397b3d 100644
---- a/test/home.test.js
++++ b/test/home.test.js
@@@ -110,7 +110,7 @@ const active = (file) => fs.readFileSync(path.join(__dirname, '..', 'deploy', 'n
t.test('the vhost still proxies the API, the run receipt and the console; the app answers and redirects the pages', () => {
const conf = active('ai.openvibe.services.conf');
for (const loc of ['location = /stats', 'location = /updates', 'location = /terms', 'location = /privacy', 'location = /dmca', 'location = /sitemap.xml', 'location = /llms.txt', 'location = /api/v1/creations ', 'location = /api/v1/creations.md ', 'location /runs/ {', 'location /console {', 'location /auth/ {', 'location /shared/']) assert.ok(conf.includes(loc), loc);
-- const ops = conf.indexOf('location ~ ^/api/v1/(chat|generate|summarize|classify|extract|embed)$ {');
++ const ops = conf.indexOf('location ~ ^/api/v1/(chat|generate|summarize|classify|extract|embed|decisions)$ {');
const run = conf.indexOf('location ~ ^/api/v1/runs/[A-Za-z0-9_-]+$ {');
const completions = conf.indexOf('location = /v1/chat/completions {');
const models = conf.indexOf('location = /v1/models {');
ddiff --git a/test/vhost-catchall.test.js b/test/vhost-catchall.test.js
iindex 46846a8..cf0ab8e 100644
---- a/test/vhost-catchall.test.js
++++ b/test/vhost-catchall.test.js
@@@ -14,4 +14,8 @@ assert.match(conf, /location ~\* \^\/metrics\(\/\|\$\) \{ return 404; \}/, '/met
const reports = /location = \/release-metrics \{([\s\S]*?)\n \}/.exec(conf);
assert.ok(reports && /limit_except POST \{ deny all; \}/.test(reports[1]) && /proxy_pass http:\/\/127\.0\.0\.1:4700;/.test(reports[1]), 'open tabs\' release reports (POST /release-metrics) reach the app');
assert.ok(conf.indexOf('location = /release-metrics') < conf.lastIndexOf(' location / {'), 'before the GET-only catch-all');
--console.log('vhost catch-all: GET/HEAD reach the app (favicon, 404 page); other methods refused; /metrics refused; POST /release-metrics passes');
++// 2026-11-12: Decisions is a public API (Exchange's /decisions page names it), so it passes like the direct operations.
++const apps = /location ~ \^\/api\/v1\/\(([a-z|]+)\)\$ \{([\s\S]*?)\n \}/.exec(conf);
++assert.ok(apps && apps[1].split('|').includes('decisions'), 'POST /api/v1/decisions reaches the app');
++assert.ok(/limit_except POST \{ deny all; \}/.test(apps[2]) && /limit_req zone=ovai_apps/.test(apps[2]), 'POST only, rate limited like the other operations');
++console.log('vhost catch-all: GET/HEAD reach the app (favicon, 404 page); other methods refused; /metrics refused; POST /release-metrics passes; POST /api/v1/decisions passes');