diff --git a/.github/pravila.md b/.github/pravila.md index 1804655..b08ba1f 100644 --- a/.github/pravila.md +++ b/.github/pravila.md @@ -49,7 +49,11 @@ ssh -i ~/.ssh/naeel_vm_id_ed25519 -o StrictHostKeyChecking=no -o ConnectTimeout= ## Git -Коммитить и пушить через SSH после каждого завершённого этапа. +⛔⛔⛔ АБСОЛЮТНОЕ ПРАВИЛО: +- Git — ТОЛЬКО ЛОКАЛЬНО в `~/fission`. НИКОГДА через SSH на VM. +- Разрешены ТОЛЬКО две операции: `git commit` и `git push`. +- ЗАПРЕЩЕНО: git pull, git fetch, git rebase, git merge, git reset, git stash, git checkout — что угодно кроме commit и push. +- Если push отклонён — СТОП, доложить пользователю. Не лезть в pull/merge/rebase самостоятельно. Версионирование тегами: `vMAJOR.MINOR.PATCH` - Patch — любое изменение кода diff --git a/console/deploy/console.yaml b/console/deploy/console.yaml index 43d6770..289ec34 100644 --- a/console/deploy/console.yaml +++ b/console/deploy/console.yaml @@ -52,7 +52,7 @@ spec: serviceAccountName: fission-console containers: - name: console - image: naeel/fission-console:v1.3.34 + image: naeel/fission-console:v1.3.36 imagePullPolicy: Always ports: - containerPort: 8090 diff --git a/console/internal/auth/auth.go b/console/internal/auth/auth.go index 2f75662..e7c0b8a 100644 --- a/console/internal/auth/auth.go +++ b/console/internal/auth/auth.go @@ -223,7 +223,13 @@ func (d *DemoAuthenticator) Authenticate(_ context.Context, login, _ string) (Us if len([]rune(login)) < 6 { return UserIdentity{}, fmt.Errorf("login must be at least 6 characters") } - return UserIdentity{Sub: uuidV5(login), Email: demoEmail(login)}, nil + sub := uuidV5(login) + // Если логин начинается с "test-" — помечаем Sub так же, чтобы + // NamespaceForSub мог создать fission-test-* namespace. + if strings.HasPrefix(login, "test-") { + sub = "test-" + sub + } + return UserIdentity{Sub: sub, Email: demoEmail(login)}, nil } // --- MultiAuthenticator --- diff --git a/console/ui/index.html b/console/ui/index.html index 213b3cf..88ee63a 100644 --- a/console/ui/index.html +++ b/console/ui/index.html @@ -100,7 +100,7 @@
:1
+module.exports = function(ctx) { const x = @@@; };
+
+[PASS] N04 | nodejs | exp=fail got=False | :1
+module.exports = function(ctx) {
+
+
+SyntaxError: Unexpected
+[PASS] N05 | nodejs | exp=fail got=False | :1
+const x = {a: 1,, b: 2};
+ ^
+
+SyntaxError: Unexpected token ','
+ at wrapSa
+[PASS] N06 | nodejs | exp=fail got=False | :1
+function foo( { return 1; }
+ ^
+
+SyntaxError: Unexpected number
+ at
+
+=== PYTHON ===
+[PASS] P01 | python | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] P02 | python | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] P03 | python | exp=fail got=False | Sorry: IndentationError: expected an indented block after function definition on line 1 (fission-lin
+[PASS] P04 | python | exp=fail got=False | File "", line 2
+ x = @@@
+ ^
+SyntaxError: invalid syntax
+[PASS] P05 | python | exp=fail got=False | File "", line 1
+ def main(data)
+ ^
+SyntaxError: expected ':'
+[PASS] P06 | python | exp=fail got=False | File "", line 2
+ def main(
+ ^
+SyntaxError: '(' was never closed
+
+=== RUBY ===
+[PASS] R01 | ruby | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] R02 | ruby | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] R03 | ruby | exp=fail got=False | ruby: :2: syntax error, unexpected {, expecting ')' (SyntaxError)
+ { status: 200 }
+ ^
+[PASS] R04 | ruby | exp=fail got=False | ruby: :2: syntax error, unexpected local variable or method, expecting '}' (SyntaxError)
+ { s
+[PASS] R05 | ruby | exp=fail got=False | ruby: :3: syntax error, unexpected `end', expecting ')' (SyntaxError)
+
+=== PHP ===
+[PASS] H01 | php | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] H02 | php | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] H03 | php | exp=fail got=False | PHP Parse error: syntax error, unexpected token "return" in on line 4
+Errors parsing
+[PASS] H04 | php | exp=fail got=False | PHP Parse error: syntax error, unexpected token ";" in on line 2
+Errors parsing
+[PASS] H05 | php | exp=fail got=False | PHP Parse error: syntax error, unexpected token "{", expecting variable in on line 2
+Errors
+
+=== GO ===
+[PASS] G01 | go | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] G02 | go | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] G03 | go | exp=fail got=False | code.go:2:15: expected ')', found '{' (and 3 more errors)
+[PASS] G04 | go | exp=fail got=False | code.go:4:1: missing import path (and 1 more errors)
+[PASS] G05 | go | exp=fail got=False | code.go:3:10: expected operand, found 'ILLEGAL' (and 6 more errors)
+
+=== ГРАНИЧНЫЕ ===
+[PASS] E01 | nodejs | exp=fail got=False | Код пустой.
+[PASS] E02 | python | exp=fail got=False | File "", line 1
+ asd @@@
+ ^
+SyntaxError: invalid syntax
+[PASS] E03 | ruby | exp=fail got=False | ruby: :1: syntax error, unexpected ';', expecting ')' (SyntaxError)
+def foo(;;; end
+ ^
+[PASS] E04 | php | exp=fail got=False | PHP Parse error: syntax error, unexpected end of file in on line 1
+Errors parsing
+
+========================
+PASS=31 FAIL=0
+========================
+[0;32m ✓ test_linters.sh — PASSED[0m
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ ЗАПУСК: test_stress.sh
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+========================================
+ test_stress.sh run=d08a7e02 users=8 invoke_parallel=8
+========================================
+
+>>> [1/3] 8 пользователей создают функции параллельно...
+ PASS: Параллельный CREATE: все 8 пользователей → 201
+
+>>> [2/3] Изоляция: каждый видит только свою функцию
+ PASS: Изоляция: все 8 пользователей видят только свои функции
+
+>>> [3/3] 8 параллельных invoke для livetest@test.local
+Ждём прогрева 8 функций...
+ прогрев функции 1/8: попытка 1/12...
+ прогрев функции 1/8: попытка 2/12...
+ прогрев функции 1/8: попытка 3/12...
+ прогрев функции 1/8: попытка 4/12...
+ прогрев функции 1/8: попытка 5/12...
+ прогрев функции 1/8: попытка 6/12...
+ прогрев функции 1/8: попытка 7/12...
+ прогрев функции 1/8: попытка 8/12...
+ прогрев функции 1/8: попытка 9/12...
+ прогрев функции 1/8: попытка 10/12...
+ прогрев функции 1/8: попытка 11/12...
+ прогрев функции 1/8: попытка 12/12...
+ функция 1/8 — готова
+ прогрев функции 2/8: попытка 1/12...
+ прогрев функции 2/8: попытка 2/12...
+ прогрев функции 2/8: попытка 3/12...
+ прогрев функции 2/8: попытка 4/12...
+ прогрев функции 2/8: попытка 5/12...
+ прогрев функции 2/8: попытка 6/12...
+ прогрев функции 2/8: попытка 7/12...
+ прогрев функции 2/8: попытка 8/12...
+ прогрев функции 2/8: попытка 9/12...
+ прогрев функции 2/8: попытка 10/12...
+ прогрев функции 2/8: попытка 11/12...
+ прогрев функции 2/8: попытка 12/12...
+ функция 2/8 — готова
+ прогрев функции 3/8: попытка 1/12...
+ прогрев функции 3/8: попытка 2/12...
+ прогрев функции 3/8: попытка 3/12...
+ прогрев функции 3/8: попытка 4/12...
+ прогрев функции 3/8: попытка 5/12...
+ прогрев функции 3/8: попытка 6/12...
+ прогрев функции 3/8: попытка 7/12...
+ прогрев функции 3/8: попытка 8/12...
+ прогрев функции 3/8: попытка 9/12...
+ прогрев функции 3/8: попытка 10/12...
+ прогрев функции 3/8: попытка 11/12...
+ прогрев функции 3/8: попытка 12/12...
+ функция 3/8 — готова
+ прогрев функции 4/8: попытка 1/12...
+ прогрев функции 4/8: попытка 2/12...
+ прогрев функции 4/8: попытка 3/12...
+ прогрев функции 4/8: попытка 4/12...
+ прогрев функции 4/8: попытка 5/12...
+ прогрев функции 4/8: попытка 6/12...
+ прогрев функции 4/8: попытка 7/12...
+ прогрев функции 4/8: попытка 8/12...
+ прогрев функции 4/8: попытка 9/12...
+ прогрев функции 4/8: попытка 10/12...
+ прогрев функции 4/8: попытка 11/12...
+ прогрев функции 4/8: попытка 12/12...
+ функция 4/8 — готова
+ прогрев функции 5/8: попытка 1/12...
+ прогрев функции 5/8: попытка 2/12...
+ прогрев функции 5/8: попытка 3/12...
+ прогрев функции 5/8: попытка 4/12...
+ прогрев функции 5/8: попытка 5/12...
+ прогрев функции 5/8: попытка 6/12...
+ прогрев функции 5/8: попытка 7/12...
+ прогрев функции 5/8: попытка 8/12...
+ прогрев функции 5/8: попытка 9/12...
+ прогрев функции 5/8: попытка 10/12...
+ прогрев функции 5/8: попытка 11/12...
+ прогрев функции 5/8: попытка 12/12...
+ функция 5/8 — готова
+ прогрев функции 6/8: попытка 1/12...
+ прогрев функции 6/8: попытка 2/12...
+ прогрев функции 6/8: попытка 3/12...
+ прогрев функции 6/8: попытка 4/12...
+ прогрев функции 6/8: попытка 5/12...
+ прогрев функции 6/8: попытка 6/12...
+ прогрев функции 6/8: попытка 7/12...
+ прогрев функции 6/8: попытка 8/12...
+ прогрев функции 6/8: попытка 9/12...
+ прогрев функции 6/8: попытка 10/12...
+ прогрев функции 6/8: попытка 11/12...
+ прогрев функции 6/8: попытка 12/12...
+ функция 6/8 — готова
+ прогрев функции 7/8: попытка 1/12...
+ прогрев функции 7/8: попытка 2/12...
+ прогрев функции 7/8: попытка 3/12...
+ прогрев функции 7/8: попытка 4/12...
+ прогрев функции 7/8: попытка 5/12...
+ прогрев функции 7/8: попытка 6/12...
+ прогрев функции 7/8: попытка 7/12...
+ прогрев функции 7/8: попытка 8/12...
+ прогрев функции 7/8: попытка 9/12...
+ прогрев функции 7/8: попытка 10/12...
+ прогрев функции 7/8: попытка 11/12...
+ прогрев функции 7/8: попытка 12/12...
+ функция 7/8 — готова
+ прогрев функции 8/8: попытка 1/12...
+ прогрев функции 8/8: попытка 2/12...
+ прогрев функции 8/8: попытка 3/12...
+ прогрев функции 8/8: попытка 4/12...
+ прогрев функции 8/8: попытка 5/12...
+ прогрев функции 8/8: попытка 6/12...
+ прогрев функции 8/8: попытка 7/12...
+ прогрев функции 8/8: попытка 8/12...
+ прогрев функции 8/8: попытка 9/12...
+ прогрев функции 8/8: попытка 10/12...
+ прогрев функции 8/8: попытка 11/12...
+ прогрев функции 8/8: попытка 12/12...
+ функция 8/8 — готова
+ FAIL: Параллельный INVOKE (livetest): 8/8 упали
+
+========================================
+ ИТОГ: PASS=2 FAIL=1
+========================================
+[0;31m ✗ test_stress.sh — FAILED (exit 1)[0m
+
+╔══════════════════════════════════════╗
+║ Скриптов: PASS=4 FAIL=4 1224s ║
+╚══════════════════════════════════════╝
+
+Упавшие скрипты:
+ [0;31m✗ test_isolation.sh[0m
+ [0;31m✗ test_update.sh[0m
+ [0;31m✗ test_invoke.sh[0m
+ [0;31m✗ test_stress.sh[0m
diff --git a/test-results/2026-05-03_19-27.log b/test-results/2026-05-03_19-27.log
new file mode 100644
index 0000000..33cbc3d
--- /dev/null
+++ b/test-results/2026-05-03_19-27.log
@@ -0,0 +1,563 @@
+LOG: /home/naeel/fission/scripts/../test-results/2026-05-03_19-27-29.log
+
+>>> Предварительная очистка тест-namespace-ов...
+pre_cleanup.sh: тест-namespace-ы не найдены, пропускаем.
+
+╔══════════════════════════════════════╗
+║ fission-console — полный тест-сьют ║
+╚══════════════════════════════════════╝
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ ЗАПУСК: test_auth.sh
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+========================================
+ test_auth.sh run=60c056c8
+========================================
+
+>>> POST /auth: пустой токен → 400
+ PASS: POST /auth пустой token → 400 (HTTP 400)
+
+>>> POST /auth: токен < 6 символов → 401
+ PASS: POST /auth токен 2 символа → 401 (HTTP 401)
+ PASS: POST /auth токен 5 символов → 401 (HTTP 401)
+
+>>> POST /auth: токен ≥6 символов → 200
+ PASS: POST /auth валидный токен → 200 (HTTP 200)
+ PASS: POST /auth namespace=fission-...
+ PASS: POST /auth ok=true
+
+>>> POST /auth: невалидный JSON → 400
+ PASS: POST /auth невалидный JSON → 400
+
+>>> POST /auth: GET метод → 405
+ PASS: GET /auth → 405
+
+>>> X-Auth-Token: отсутствует → 401
+ PASS: GET /functions без токена → 401 (HTTP 401)
+
+>>> X-Auth-Token: токен < 6 символов → 401
+ PASS: GET /functions токен 2 символа → 401 (HTTP 401)
+ PASS: GET /functions токен 5 символов → 401 (HTTP 401)
+
+>>> X-Auth-Token: токен ≥6 символов → 200
+ PASS: GET /functions валидный X-Auth-Token → 200 (HTTP 200)
+
+>>> Authorization: Bearer → 200
+ PASS: GET /functions Authorization: Bearer → 200 (HTTP 200)
+
+>>> Namespace детерминирован (одинаковый токен → одинаковый NS)
+ PASS: Namespace детерминирован: fission-test-eefba24734b47f9a
+
+========================================
+ ИТОГ: PASS=14 FAIL=0
+========================================
+[0;32m ✓ test_auth.sh — PASSED[0m
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ ЗАПУСК: test_functions_crud.sh
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+========================================
+ test_functions_crud.sh run=4389efa7
+========================================
+
+>>> CREATE: базовый nodejs → 201
+ PASS: CREATE nodejs → 201 (HTTP 201)
+ PASS: CREATE: name в ответе
+ PASS: CREATE: route непустой
+ PASS: CREATE: package непустой
+
+>>> CREATE: дубликат → 409
+ PASS: CREATE дубликат → 409 (HTTP 409)
+
+>>> CREATE: без name → 400
+ PASS: CREATE без name → 400 (HTTP 400)
+
+>>> CREATE: без language → 400
+ PASS: CREATE без language → 400 (HTTP 400)
+
+>>> CREATE: без code → 400
+ PASS: CREATE без code → 400 (HTTP 400)
+
+>>> CREATE: невалидный язык → 400
+ PASS: CREATE язык=cobol → 400 (HTTP 400)
+
+>>> CREATE: валидация имени
+ PASS: CREATE имя с заглавной → 400 (HTTP 400)
+ PASS: CREATE имя с ведущим дефисом → 400 (HTTP 400)
+ PASS: CREATE имя с завершающим дефисом → 400 (HTTP 400)
+ PASS: CREATE имя с пробелом → 400 (HTTP 400)
+ PASS: CREATE имя 58 символов → 400 (HTTP 400)
+ PASS: CREATE имя 57 символов → 201 (HTTP 201)
+
+>>> CREATE: код > 1MB → 400 или 413
+ PASS: CREATE код > 1MB → отклонён (400)
+
+>>> LIST: GET /functions → 200, массив
+ PASS: LIST GET /functions → 200 (HTTP 200)
+ PASS: LIST: содержит ≥1 функцию (создали выше)
+
+>>> LIST: новый пользователь → пустой массив
+ PASS: LIST новый пользователь → 200 (HTTP 200)
+ PASS: LIST новый пользователь → []
+
+>>> GET: /functions/{name} → 200
+ PASS: GET /functions/t-crud-fn-4389efa7 → 200 (HTTP 200)
+ PASS: GET: name совпадает
+ PASS: GET: environment непустой (console-nodejs-env)
+
+>>> GET: несуществующая функция → 404
+ PASS: GET несуществующая → 404 (HTTP 404)
+
+>>> DELETE: /functions/{name} → 200
+ PASS: DELETE crud-fn → 200 (HTTP 200)
+ PASS: DELETE: deleted=true
+
+>>> DELETE: повторное удаление → 404
+ PASS: DELETE повторное → 404 (HTTP 404)
+
+>>> GET: после DELETE → 404
+ PASS: GET после DELETE → 404 (HTTP 404)
+
+>>> DELETE: несуществующая функция → 404
+ PASS: DELETE несуществующая → 404 (HTTP 404)
+
+========================================
+ ИТОГ: PASS=29 FAIL=0
+========================================
+[0;32m ✓ test_functions_crud.sh — PASSED[0m
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ ЗАПУСК: test_ttl.sh
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+========================================
+ test_ttl.sh run=41287101
+========================================
+
+>>> CREATE без TTL → expires_at отсутствует
+ PASS: CREATE без TTL: expires_at отсутствует
+
+>>> CREATE с TTL=15m → expires_at присутствует
+ PASS: CREATE TTL=15m: expires_at=2026-05-03T15:43:08Z
+ PASS: expires_at формат RFC3339
+
+>>> CREATE с TTL=1h → expires_at присутствует
+ PASS: CREATE TTL=1h: expires_at=2026-05-03T16:28:09Z
+
+>>> CREATE с TTL=2d → expires_at присутствует
+ PASS: CREATE TTL=2d: expires_at=2026-05-05T15:28:10Z
+
+>>> CREATE TTL невалидный → 400 (без создания объектов)
+ PASS: TTL='99z' → 400
+ PASS: TTL='abc' → 400
+ PASS: TTL='0d' → 400
+ PASS: TTL='-1h' → 400
+
+>>> LIST: функция с TTL видна в списке
+ PASS: LIST: видно ≥3 функции (2 с TTL, 1 без)
+
+========================================
+ ИТОГ: PASS=10 FAIL=0
+========================================
+[0;32m ✓ test_ttl.sh — PASSED[0m
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ ЗАПУСК: test_isolation.sh
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+========================================
+ test_isolation.sh run=e51b5b24
+========================================
+
+>>> Alice создаёт функцию
+ PASS: Alice CREATE t-alice-fn → 201 (HTTP 201)
+
+>>> Bob GET t-alice-fn → 404
+ PASS: Bob GET t-alice-fn → 404 (HTTP 404)
+
+>>> Bob DELETE t-alice-fn → 404
+ PASS: Bob DELETE t-alice-fn → 404 (HTTP 404)
+
+>>> Bob PUT t-alice-fn/code → 404
+ PASS: Bob PUT t-alice-fn/code → 404 (HTTP 404)
+
+>>> Bob INVOKE t-alice-fn → 404
+ PASS: Bob INVOKE t-alice-fn → 404 (HTTP 404)
+
+>>> Alice GET t-alice-fn → 200 (не повреждена)
+ PASS: Alice GET t-alice-fn → 200 (HTTP 200)
+
+>>> LIST изоляция: Alice и Bob видят только свои функции
+ FAIL: Alice LIST не содержит t-alice-fn (count=1)
+ PASS: Alice LIST не содержит t-bob-fn
+ FAIL: Bob LIST не содержит t-bob-fn
+ PASS: Bob LIST не содержит t-alice-fn
+
+>>> Route изоляция: одинаковое имя → разные routes
+ PASS: Route изоляция: /3a28b708954b/t-alice-fn ≠ /cd66c0880a7a/t-bob-fn
+
+========================================
+ ИТОГ: PASS=9 FAIL=2
+========================================
+[0;31m ✗ test_isolation.sh — FAILED (exit 1)[0m
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ ЗАПУСК: test_update.sh
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+========================================
+ test_update.sh run=e557f2e3
+========================================
+
+>>> CREATE: версия v1
+ PASS: CREATE v1 → 201 (HTTP 201)
+Ждём прогрева...
+ TRY=1 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=2 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=3 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=4 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=5 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=6 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=7 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=8 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=9 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=10 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=11 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=12 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ FAIL: Invoke v1 не стабилизировался
+
+>>> PUT /code: версия v2
+ PASS: PUT /code v2 → 200 (HTTP 200)
+ PASS: PUT /code: updated=true
+ PASS: PUT /code: новый package=t-upd-fn-e557f2e3-pkg-mopxfjxq
+Ждём пока executor подхватит новый код...
+ TRY=1 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=2 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=3 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=4 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=5 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=6 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=7 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=8 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=9 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=10 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=11 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=12 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=13 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=14 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=15 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ FAIL: Invoke после update не вернул version-two
+
+>>> PUT /code: пустой код → 400
+ PASS: PUT /code пустой код → 400 (HTTP 400)
+ PASS: PUT /code пробельный код → 400 (HTTP 400)
+
+>>> PUT /code: несуществующая функция → 404
+ PASS: PUT /code несуществующая → 404 (HTTP 404)
+
+>>> PUT /code: версия v3 (цепочка)
+ TRY=1 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=2 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=3 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=4 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=5 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=6 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=7 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=8 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=9 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=10 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=11 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=12 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=13 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=14 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ TRY=15 fn=t-upd-fn-e557f2e3 status=404 raw=404 page not found err=
+ FAIL: Invoke после v3 update не стабилизировался
+
+========================================
+ ИТОГ: PASS=7 FAIL=3
+========================================
+[0;31m ✗ test_update.sh — FAILED (exit 1)[0m
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ ЗАПУСК: test_invoke.sh
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+========================================
+ test_invoke.sh run=b9f17c9f
+========================================
+
+>>> INVOKE несуществующей функции → 404
+ PASS: INVOKE несуществующей → 404 (HTTP 404)
+
+>>> NodeJS: create + invoke
+ PASS: NodeJS CREATE → 201 (HTTP 201)
+ TRY=1 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=2 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=3 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=4 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=5 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=6 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=7 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=8 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=9 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=10 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=11 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=12 fn=t-node-fn-b9f17c9f status=404 raw=404 page not found err=
+ FAIL: NodeJS invoke не стабилизировался за 60s
+ FAIL: NodeJS второй invoke → status=404
+
+>>> Python: create + invoke
+ PASS: Python CREATE → 201 (HTTP 201)
+ TRY=1 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=2 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=3 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=4 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=5 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=6 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=7 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=8 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=9 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=10 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=11 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=12 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=13 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=14 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ TRY=15 fn=t-py-fn-b9f17c9f status=404 raw=404 page not found err=
+ FAIL: Python invoke не стабилизировался за 75s
+
+>>> Invoke response fields
+ FAIL: invoke.status = 404
+ PASS: invoke.latency_ms = 1 ms
+ PASS: invoke.response_raw непустой
+
+========================================
+ ИТОГ: PASS=5 FAIL=4
+========================================
+[0;31m ✗ test_invoke.sh — FAILED (exit 1)[0m
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ ЗАПУСК: test_linters.sh
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+=== NODEJS ===
+[PASS] N01 | nodejs | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] N02 | nodejs | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] N03 | nodejs | exp=fail got=False | :1
+module.exports = function(ctx) { const x = @@@; };
+
+[PASS] N04 | nodejs | exp=fail got=False | :1
+module.exports = function(ctx) {
+
+
+SyntaxError: Unexpected
+[PASS] N05 | nodejs | exp=fail got=False | :1
+const x = {a: 1,, b: 2};
+ ^
+
+SyntaxError: Unexpected token ','
+ at wrapSa
+[PASS] N06 | nodejs | exp=fail got=False | :1
+function foo( { return 1; }
+ ^
+
+SyntaxError: Unexpected number
+ at
+
+=== PYTHON ===
+[PASS] P01 | python | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] P02 | python | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] P03 | python | exp=fail got=False | Sorry: IndentationError: expected an indented block after function definition on line 1 (fission-lin
+[PASS] P04 | python | exp=fail got=False | File "", line 2
+ x = @@@
+ ^
+SyntaxError: invalid syntax
+[PASS] P05 | python | exp=fail got=False | File "", line 1
+ def main(data)
+ ^
+SyntaxError: expected ':'
+[PASS] P06 | python | exp=fail got=False | File "", line 2
+ def main(
+ ^
+SyntaxError: '(' was never closed
+
+=== RUBY ===
+[PASS] R01 | ruby | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] R02 | ruby | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] R03 | ruby | exp=fail got=False | ruby: :2: syntax error, unexpected {, expecting ')' (SyntaxError)
+ { status: 200 }
+ ^
+[PASS] R04 | ruby | exp=fail got=False | ruby: :2: syntax error, unexpected local variable or method, expecting '}' (SyntaxError)
+ { s
+[PASS] R05 | ruby | exp=fail got=False | ruby: :3: syntax error, unexpected `end', expecting ')' (SyntaxError)
+
+=== PHP ===
+[PASS] H01 | php | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] H02 | php | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] H03 | php | exp=fail got=False | PHP Parse error: syntax error, unexpected token "return" in on line 4
+Errors parsing
+[PASS] H04 | php | exp=fail got=False | PHP Parse error: syntax error, unexpected token ";" in on line 2
+Errors parsing
+[PASS] H05 | php | exp=fail got=False | PHP Parse error: syntax error, unexpected token "{", expecting variable in on line 2
+Errors
+
+=== GO ===
+[PASS] G01 | go | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] G02 | go | exp=ok got=True | ✅ Синтаксис корректен.
+[PASS] G03 | go | exp=fail got=False | code.go:2:15: expected ')', found '{' (and 3 more errors)
+[PASS] G04 | go | exp=fail got=False | code.go:4:1: missing import path (and 1 more errors)
+[PASS] G05 | go | exp=fail got=False | code.go:3:10: expected operand, found 'ILLEGAL' (and 6 more errors)
+
+=== ГРАНИЧНЫЕ ===
+[PASS] E01 | nodejs | exp=fail got=False | Код пустой.
+[PASS] E02 | python | exp=fail got=False | File "", line 1
+ asd @@@
+ ^
+SyntaxError: invalid syntax
+[PASS] E03 | ruby | exp=fail got=False | ruby: :1: syntax error, unexpected ';', expecting ')' (SyntaxError)
+def foo(;;; end
+ ^
+[PASS] E04 | php | exp=fail got=False | PHP Parse error: syntax error, unexpected end of file in on line 1
+Errors parsing
+
+========================
+PASS=31 FAIL=0
+========================
+[0;32m ✓ test_linters.sh — PASSED[0m
+
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+ ЗАПУСК: test_stress.sh
+━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
+
+========================================
+ test_stress.sh run=d08a7e02 users=8 invoke_parallel=8
+========================================
+
+>>> [1/3] 8 пользователей создают функции параллельно...
+ PASS: Параллельный CREATE: все 8 пользователей → 201
+
+>>> [2/3] Изоляция: каждый видит только свою функцию
+ PASS: Изоляция: все 8 пользователей видят только свои функции
+
+>>> [3/3] 8 параллельных invoke для livetest@test.local
+Ждём прогрева 8 функций...
+ прогрев функции 1/8: попытка 1/12...
+ прогрев функции 1/8: попытка 2/12...
+ прогрев функции 1/8: попытка 3/12...
+ прогрев функции 1/8: попытка 4/12...
+ прогрев функции 1/8: попытка 5/12...
+ прогрев функции 1/8: попытка 6/12...
+ прогрев функции 1/8: попытка 7/12...
+ прогрев функции 1/8: попытка 8/12...
+ прогрев функции 1/8: попытка 9/12...
+ прогрев функции 1/8: попытка 10/12...
+ прогрев функции 1/8: попытка 11/12...
+ прогрев функции 1/8: попытка 12/12...
+ функция 1/8 — готова
+ прогрев функции 2/8: попытка 1/12...
+ прогрев функции 2/8: попытка 2/12...
+ прогрев функции 2/8: попытка 3/12...
+ прогрев функции 2/8: попытка 4/12...
+ прогрев функции 2/8: попытка 5/12...
+ прогрев функции 2/8: попытка 6/12...
+ прогрев функции 2/8: попытка 7/12...
+ прогрев функции 2/8: попытка 8/12...
+ прогрев функции 2/8: попытка 9/12...
+ прогрев функции 2/8: попытка 10/12...
+ прогрев функции 2/8: попытка 11/12...
+ прогрев функции 2/8: попытка 12/12...
+ функция 2/8 — готова
+ прогрев функции 3/8: попытка 1/12...
+ прогрев функции 3/8: попытка 2/12...
+ прогрев функции 3/8: попытка 3/12...
+ прогрев функции 3/8: попытка 4/12...
+ прогрев функции 3/8: попытка 5/12...
+ прогрев функции 3/8: попытка 6/12...
+ прогрев функции 3/8: попытка 7/12...
+ прогрев функции 3/8: попытка 8/12...
+ прогрев функции 3/8: попытка 9/12...
+ прогрев функции 3/8: попытка 10/12...
+ прогрев функции 3/8: попытка 11/12...
+ прогрев функции 3/8: попытка 12/12...
+ функция 3/8 — готова
+ прогрев функции 4/8: попытка 1/12...
+ прогрев функции 4/8: попытка 2/12...
+ прогрев функции 4/8: попытка 3/12...
+ прогрев функции 4/8: попытка 4/12...
+ прогрев функции 4/8: попытка 5/12...
+ прогрев функции 4/8: попытка 6/12...
+ прогрев функции 4/8: попытка 7/12...
+ прогрев функции 4/8: попытка 8/12...
+ прогрев функции 4/8: попытка 9/12...
+ прогрев функции 4/8: попытка 10/12...
+ прогрев функции 4/8: попытка 11/12...
+ прогрев функции 4/8: попытка 12/12...
+ функция 4/8 — готова
+ прогрев функции 5/8: попытка 1/12...
+ прогрев функции 5/8: попытка 2/12...
+ прогрев функции 5/8: попытка 3/12...
+ прогрев функции 5/8: попытка 4/12...
+ прогрев функции 5/8: попытка 5/12...
+ прогрев функции 5/8: попытка 6/12...
+ прогрев функции 5/8: попытка 7/12...
+ прогрев функции 5/8: попытка 8/12...
+ прогрев функции 5/8: попытка 9/12...
+ прогрев функции 5/8: попытка 10/12...
+ прогрев функции 5/8: попытка 11/12...
+ прогрев функции 5/8: попытка 12/12...
+ функция 5/8 — готова
+ прогрев функции 6/8: попытка 1/12...
+ прогрев функции 6/8: попытка 2/12...
+ прогрев функции 6/8: попытка 3/12...
+ прогрев функции 6/8: попытка 4/12...
+ прогрев функции 6/8: попытка 5/12...
+ прогрев функции 6/8: попытка 6/12...
+ прогрев функции 6/8: попытка 7/12...
+ прогрев функции 6/8: попытка 8/12...
+ прогрев функции 6/8: попытка 9/12...
+ прогрев функции 6/8: попытка 10/12...
+ прогрев функции 6/8: попытка 11/12...
+ прогрев функции 6/8: попытка 12/12...
+ функция 6/8 — готова
+ прогрев функции 7/8: попытка 1/12...
+ прогрев функции 7/8: попытка 2/12...
+ прогрев функции 7/8: попытка 3/12...
+ прогрев функции 7/8: попытка 4/12...
+ прогрев функции 7/8: попытка 5/12...
+ прогрев функции 7/8: попытка 6/12...
+ прогрев функции 7/8: попытка 7/12...
+ прогрев функции 7/8: попытка 8/12...
+ прогрев функции 7/8: попытка 9/12...
+ прогрев функции 7/8: попытка 10/12...
+ прогрев функции 7/8: попытка 11/12...
+ прогрев функции 7/8: попытка 12/12...
+ функция 7/8 — готова
+ прогрев функции 8/8: попытка 1/12...
+ прогрев функции 8/8: попытка 2/12...
+ прогрев функции 8/8: попытка 3/12...
+ прогрев функции 8/8: попытка 4/12...
+ прогрев функции 8/8: попытка 5/12...
+ прогрев функции 8/8: попытка 6/12...
+ прогрев функции 8/8: попытка 7/12...
+ прогрев функции 8/8: попытка 8/12...
+ прогрев функции 8/8: попытка 9/12...
+ прогрев функции 8/8: попытка 10/12...
+ прогрев функции 8/8: попытка 11/12...
+ прогрев функции 8/8: попытка 12/12...
+ функция 8/8 — готова
+ FAIL: Параллельный INVOKE (livetest): 8/8 упали
+
+========================================
+ ИТОГ: PASS=2 FAIL=1
+========================================
+[0;31m ✗ test_stress.sh — FAILED (exit 1)[0m
+
+╔══════════════════════════════════════╗
+║ Скриптов: PASS=4 FAIL=4 1224s ║
+╚══════════════════════════════════════╝
+
+Упавшие скрипты:
+ [0;31m✗ test_isolation.sh[0m
+ [0;31m✗ test_update.sh[0m
+ [0;31m✗ test_invoke.sh[0m
+ [0;31m✗ test_stress.sh[0m
diff --git a/test-results/2026-05-03_20-11-invoke.log b/test-results/2026-05-03_20-11-invoke.log
new file mode 100644
index 0000000..2b30cc7
--- /dev/null
+++ b/test-results/2026-05-03_20-11-invoke.log
@@ -0,0 +1,27 @@
+
+========================================
+ test_invoke.sh run=be1c4858
+========================================
+
+>>> INVOKE несуществующей функции → 404
+ PASS: INVOKE несуществующей → 404 (HTTP 404)
+
+>>> NodeJS: create + invoke
+ PASS: NodeJS CREATE → 201 (HTTP 201)
+ TRY=1 fn=t-node-fn-be1c4858 status=200 raw=node-invoke-ok err=
+ PASS: NodeJS invoke → 200, тело содержит node-invoke-ok
+ PASS: NodeJS второй invoke → 200 (warm)
+
+>>> Python: create + invoke
+ PASS: Python CREATE → 201 (HTTP 201)
+ TRY=1 fn=t-py-fn-be1c4858 status=200 raw=py-invoke-ok err=
+ PASS: Python invoke → 200, тело содержит py-invoke-ok
+
+>>> Invoke response fields
+ PASS: invoke.status = 200
+ PASS: invoke.latency_ms = 10 ms
+ PASS: invoke.response_raw непустой
+
+========================================
+ ИТОГ: PASS=9 FAIL=0
+========================================
diff --git a/test-results/2026-05-03_20-11-isolation.log b/test-results/2026-05-03_20-11-isolation.log
new file mode 100644
index 0000000..2faae5f
--- /dev/null
+++ b/test-results/2026-05-03_20-11-isolation.log
@@ -0,0 +1,35 @@
+
+========================================
+ test_isolation.sh run=d680fd2c
+========================================
+
+>>> Alice создаёт функцию
+ PASS: Alice CREATE t-alice-fn → 201 (HTTP 201)
+
+>>> Bob GET t-alice-fn → 404
+ PASS: Bob GET t-alice-fn → 404 (HTTP 404)
+
+>>> Bob DELETE t-alice-fn → 404
+ PASS: Bob DELETE t-alice-fn → 404 (HTTP 404)
+
+>>> Bob PUT t-alice-fn/code → 404
+ PASS: Bob PUT t-alice-fn/code → 404 (HTTP 404)
+
+>>> Bob INVOKE t-alice-fn → 404
+ PASS: Bob INVOKE t-alice-fn → 404 (HTTP 404)
+
+>>> Alice GET t-alice-fn → 200 (не повреждена)
+ PASS: Alice GET t-alice-fn → 200 (HTTP 200)
+
+>>> LIST изоляция: Alice и Bob видят только свои функции
+ PASS: Alice LIST содержит t-alice-fn
+ PASS: Alice LIST не содержит t-bob-fn
+ PASS: Bob LIST содержит t-bob-fn
+ PASS: Bob LIST не содержит t-alice-fn
+
+>>> Route изоляция: одинаковое имя → разные routes
+ PASS: Route изоляция: /90bae4a0946a/t-alice-fn ≠ /7244c3bb8313/t-bob-fn
+
+========================================
+ ИТОГ: PASS=11 FAIL=0
+========================================
diff --git a/test-results/2026-05-03_20-12-stress.log b/test-results/2026-05-03_20-12-stress.log
new file mode 100644
index 0000000..3d1d3c7
--- /dev/null
+++ b/test-results/2026-05-03_20-12-stress.log
@@ -0,0 +1,26 @@
+
+========================================
+ test_stress.sh run=7c90f8d1 users=8 invoke_parallel=8
+========================================
+
+>>> [1/3] 8 пользователей создают функции параллельно...
+ PASS: Параллельный CREATE: все 8 пользователей → 201
+
+>>> [2/3] Изоляция: каждый видит только свою функцию
+ PASS: Изоляция: все 8 пользователей видят только свои функции
+
+>>> [3/3] 8 параллельных invoke для livetest@test.local
+Ждём прогрева 8 функций...
+ функция 1/8 — готова
+ функция 2/8 — готова
+ функция 3/8 — готова
+ функция 4/8 — готова
+ функция 5/8 — готова
+ функция 6/8 — готова
+ функция 7/8 — готова
+ функция 8/8 — готова
+ PASS: Параллельный INVOKE (livetest): 8/8 OK
+
+========================================
+ ИТОГ: PASS=3 FAIL=0
+========================================
diff --git a/test-results/2026-05-03_20-12-update.log b/test-results/2026-05-03_20-12-update.log
new file mode 100644
index 0000000..e9adc58
--- /dev/null
+++ b/test-results/2026-05-03_20-12-update.log
@@ -0,0 +1,33 @@
+
+========================================
+ test_update.sh run=8950afbb
+========================================
+
+>>> CREATE: версия v1
+ PASS: CREATE v1 → 201 (HTTP 201)
+Ждём прогрева...
+ TRY=1 fn=t-upd-fn-8950afbb status=200 raw=version-one err=
+ PASS: Invoke v1 → version-one
+
+>>> PUT /code: версия v2
+ PASS: PUT /code v2 → 200 (HTTP 200)
+ PASS: PUT /code: updated=true
+ PASS: PUT /code: новый package=t-upd-fn-8950afbb-pkg-mopyyge1
+Ждём пока executor подхватит новый код...
+ TRY=1 fn=t-upd-fn-8950afbb status=200 raw=version-two err=
+ PASS: Invoke после update → version-two
+
+>>> PUT /code: пустой код → 400
+ PASS: PUT /code пустой код → 400 (HTTP 400)
+ PASS: PUT /code пробельный код → 400 (HTTP 400)
+
+>>> PUT /code: несуществующая функция → 404
+ PASS: PUT /code несуществующая → 404 (HTTP 404)
+
+>>> PUT /code: версия v3 (цепочка)
+ TRY=1 fn=t-upd-fn-8950afbb status=200 raw=version-three err=
+ PASS: Invoke после v3 update → version-three
+
+========================================
+ ИТОГ: PASS=10 FAIL=0
+========================================