From 9fc2a6e3e5ba146235c158aedf8b12f31f17c132 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 15:03:15 +0200 Subject: [PATCH 01/17] chore(deps): bump typescript to 7.0.2 --- apps/mcp-server-dev/package.json | 2 +- package.json | 5 +- packages/core/package.json | 4 +- packages/create-openora/package.json | 2 +- packages/mcp/package.json | 2 +- packages/testing/package.json | 2 +- pnpm-lock.yaml | 317 +++++++++++++++++++++++---- 7 files changed, 288 insertions(+), 46 deletions(-) diff --git a/apps/mcp-server-dev/package.json b/apps/mcp-server-dev/package.json index 21e246ba..a1ec3894 100644 --- a/apps/mcp-server-dev/package.json +++ b/apps/mcp-server-dev/package.json @@ -19,6 +19,6 @@ "devDependencies": { "@types/node": "25.9.2", "@types/pg": "8.20.0", - "typescript": "6.0.3" + "typescript": "7.0.2" } } diff --git a/package.json b/package.json index f37fd377..42df91a5 100644 --- a/package.json +++ b/package.json @@ -14,7 +14,7 @@ "build": "turbo run build", "dev": "turbo run dev", "clean": "turbo run clean && rm -rf node_modules", - "verify": "turbo run check:types check:lint check:format check:boundaries check:shape check:deprecations test:unit test:tools && pnpm check:drift", + "verify": "turbo run check:types check:lint check:format check:boundaries check:shape check:deprecations test:unit test:integration test:tools && pnpm check:drift", "check:types": "turbo run check:types", "check:lint": "oxlint .", "check:format": "oxfmt --check", @@ -38,6 +38,7 @@ "db:migrate": "openora-migrate", "db:seed": "tsx tools/db/seed.ts", "db:setup:test": "tsx tools/db/setup-test-db.ts", + "db:setup:test:fresh": "tsx tools/db/setup-test-db.ts --fresh", "create:app": "tsx tools/create/create-igaming-app.ts", "create:service": "tsx tools/create/create-service.ts", "setup": "tsx tools/setup/setup-agent.ts", @@ -65,7 +66,7 @@ "rulesync": "15.1.0", "tsx": "4.23.1", "turbo": "2.10.7", - "typescript": "6.0.3" + "typescript": "7.0.2" }, "engines": { "node": ">=26.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 6e131bab..243624e0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -559,6 +559,7 @@ "check:types": "tsc --noEmit", "clean": "rm -rf dist", "test:unit": "vitest run --passWithNoTests", + "test:integration": "vitest run --config vitest.integration.config.ts --passWithNoTests", "gen:drizzle": "node scripts/generate-all.mjs" }, "dependencies": { @@ -589,7 +590,8 @@ "@types/react": "19.2.17", "@types/react-dom": "19.2.3", "drizzle-kit": "0.31.10", - "typescript": "6.0.3", + "typescript": "7.0.2", + "vite-tsconfig-paths": "6.1.1", "vitest": "4.1.8" }, "peerDependencies": { diff --git a/packages/create-openora/package.json b/packages/create-openora/package.json index 5dacad60..bdccc316 100644 --- a/packages/create-openora/package.json +++ b/packages/create-openora/package.json @@ -36,7 +36,7 @@ "devDependencies": { "@openora/core": "workspace:*", "@types/node": "25.9.2", - "typescript": "6.0.3" + "typescript": "7.0.2" }, "engines": { "node": ">=26" diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 8650b8fd..6ca5b5a3 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -38,6 +38,6 @@ }, "devDependencies": { "@types/node": "26.1.1", - "typescript": "6.0.3" + "typescript": "7.0.2" } } diff --git a/packages/testing/package.json b/packages/testing/package.json index 932e899c..5cd03dbe 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -29,7 +29,7 @@ "devDependencies": { "@types/node": "25.9.2", "@types/pg": "8.20.0", - "typescript": "6.0.3", + "typescript": "7.0.2", "vitest": "4.1.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index da50195d..ef8f2dad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,7 +20,7 @@ importers: version: 2.31.1(@types/node@26.1.1) '@commitlint/cli': specifier: 21.2.1 - version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@6.0.3) + version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2) '@commitlint/config-conventional': specifier: 21.2.0 version: 21.2.0 @@ -59,7 +59,7 @@ importers: version: 8.22.0 rulesync: specifier: 15.1.0 - version: 15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@6.0.3)) + version: 15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@7.0.2)) tsx: specifier: 4.23.1 version: 4.23.1 @@ -67,8 +67,8 @@ importers: specifier: 2.10.7 version: 2.10.7 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 apps/mcp-server-dev: dependencies: @@ -92,8 +92,8 @@ importers: specifier: 8.20.0 version: 8.20.0 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 packages/core: dependencies: @@ -180,8 +180,11 @@ importers: specifier: 0.31.10 version: 0.31.10 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 + vite-tsconfig-paths: + specifier: 6.1.1 + version: 6.1.1(typescript@7.0.2)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) vitest: specifier: 4.1.8 version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) @@ -195,8 +198,8 @@ importers: specifier: 25.9.2 version: 25.9.2 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 packages/mcp: dependencies: @@ -214,8 +217,8 @@ importers: specifier: 26.1.1 version: 26.1.1 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 packages/testing: dependencies: @@ -242,8 +245,8 @@ importers: specifier: 8.20.0 version: 8.20.0 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 vitest: specifier: 4.1.8 version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) @@ -1772,6 +1775,126 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@valibot/to-json-schema@1.7.1': resolution: {integrity: sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==} peerDependencies: @@ -2607,6 +2730,9 @@ packages: resolution: {integrity: sha512-NLvV9ubZ6NDsJaOpKPy3cQeJpKi9DcWiyCiFUpJPA0YihRqiE6RWaLUmgNNPr8MgPpLZjnBjSmou7uZBRJv9wA==} engines: {node: '>=20'} + globrex@0.1.2: + resolution: {integrity: sha512-uHJgbwAMwNFf5mLst7IWLNg14x1CkeqglJb/K3doi4dw6q2IvAAmM/Y81kevy83wP+Sst+nutFTYOGg3d1lsxg==} + gopd@1.2.0: resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==} engines: {node: '>= 0.4'} @@ -3718,6 +3844,17 @@ packages: ts-morph@28.0.0: resolution: {integrity: sha512-Wp3tnZ2bzwxyTZMtgWVzXDfm7lB1Drz+y9DmmYH/L702PQhPyVrp3pkou3yIz4qjS14GY9kcpmLiOOMvl8oG1g==} + tsconfck@3.1.6: + resolution: {integrity: sha512-ks6Vjr/jEw0P1gmOVwutM3B7fWxoWBL2KRDb1JfqGVawBmO5UsvmWOQFGHBPl5yxYz4eERr19E6L7NMv+Fej4w==} + engines: {node: ^18 || >=20} + deprecated: unmaintained + hasBin: true + peerDependencies: + typescript: ^5.0.0 + peerDependenciesMeta: + typescript: + optional: true + tsconfig-paths-webpack-plugin@4.2.0: resolution: {integrity: sha512-zbem3rfRS8BgeNK50Zz5SIQgXzLafiHjOwUAvk/38/o1jHn/V5QAgVUcz884or7WYcPaH3N2CIfUc2u0ul7UcA==} engines: {node: '>=10.13.0'} @@ -3750,9 +3887,9 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true uint8array-extras@1.5.0: @@ -3803,6 +3940,11 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + vite-tsconfig-paths@6.1.1: + resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==} + peerDependencies: + vite: '*' + vite@8.0.16: resolution: {integrity: sha512-h9bXPmJichP5fLmVQo3PyaGSDE2n3aPuomeAlVRm0JLmt4rY6zmPKd59HYI4LNW8oTK7tlTsuC7l/m7awx9Jcw==} engines: {node: ^20.19.0 || >=22.12.0} @@ -4215,12 +4357,12 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@6.0.3)': + '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2)': dependencies: '@commitlint/config-conventional': 21.2.0 '@commitlint/format': 21.2.0 '@commitlint/lint': 21.2.0 - '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@6.0.3) + '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@7.0.2) '@commitlint/read': 21.2.1(conventional-commits-parser@7.1.0) '@commitlint/types': 21.2.0 tinyexec: 1.2.4 @@ -4265,14 +4407,14 @@ snapshots: '@commitlint/rules': 21.2.0 '@commitlint/types': 21.2.0 - '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@6.0.3)': + '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@7.0.2)': dependencies: '@commitlint/config-validator': 21.2.0 '@commitlint/execute-rule': 21.0.1 '@commitlint/resolve-extends': 21.2.0 '@commitlint/types': 21.2.0 - cosmiconfig: 9.0.2(typescript@6.0.3) - cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) + cosmiconfig: 9.0.2(typescript@7.0.2) + cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2) es-toolkit: 1.48.1 is-plain-obj: 4.1.0 picocolors: 1.1.1 @@ -5471,9 +5613,69 @@ snapshots: dependencies: csstype: 3.2.3 - '@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3))': + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2))': dependencies: - valibot: 1.4.1(typescript@6.0.3) + valibot: 1.4.1(typescript@7.0.2) '@vitest/coverage-v8@4.1.8(vitest@4.1.8)': dependencies: @@ -5809,21 +6011,21 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): + cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2): dependencies: '@types/node': 26.1.1 - cosmiconfig: 9.0.2(typescript@6.0.3) + cosmiconfig: 9.0.2(typescript@7.0.2) jiti: 2.6.1 - typescript: 6.0.3 + typescript: 7.0.2 - cosmiconfig@9.0.2(typescript@6.0.3): + cosmiconfig@9.0.2(typescript@7.0.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.2.0 parse-json: 5.2.0 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 cron-parser@4.9.0: dependencies: @@ -6099,7 +6301,7 @@ snapshots: fast-uri@3.1.2: {} - fastmcp@4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4): + fastmcp@4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@standard-schema/spec': 1.1.0 @@ -6111,7 +6313,7 @@ snapshots: strict-event-emitter-types: 2.0.0 undici: 7.28.0 uri-templates: 0.2.0 - xsschema: 0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) + xsschema: 0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) yargs: 18.0.0 zod: 4.4.3 zod-to-json-schema: 3.25.2(zod@4.4.3) @@ -6264,6 +6466,8 @@ snapshots: slash: 5.1.0 unicorn-magic: 0.4.0 + globrex@0.1.2: {} + gopd@1.2.0: {} graceful-fs@4.2.11: {} @@ -7077,17 +7281,17 @@ snapshots: transitivePeerDependencies: - supports-color - rulesync@15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@6.0.3)): + rulesync@15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@7.0.2)): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@octokit/request-error': 7.1.0 '@octokit/rest': 22.0.1 '@toon-format/toon': 4.0.0 - '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@6.0.3)) + '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@7.0.2)) commander: 15.0.0 effect: 3.22.0 es-toolkit: 1.49.0 - fastmcp: 4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4) + fastmcp: 4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4) globby: 16.2.2 gray-matter: 4.0.3 js-yaml: 5.2.1 @@ -7353,6 +7557,10 @@ snapshots: '@ts-morph/common': 0.29.0 code-block-writer: 13.0.3 + tsconfck@3.1.6(typescript@7.0.2): + optionalDependencies: + typescript: 7.0.2 + tsconfig-paths-webpack-plugin@4.2.0: dependencies: chalk: 4.1.2 @@ -7395,7 +7603,28 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript@6.0.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 uint8array-extras@1.5.0: {} @@ -7417,12 +7646,22 @@ snapshots: uri-templates@0.2.0: {} - valibot@1.4.1(typescript@6.0.3): + valibot@1.4.1(typescript@7.0.2): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 vary@1.1.2: {} + vite-tsconfig-paths@6.1.1(typescript@7.0.2)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)): + dependencies: + debug: 4.4.3 + globrex: 0.1.2 + tsconfck: 3.1.6(typescript@7.0.2) + vite: 8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1) + transitivePeerDependencies: + - supports-color + - typescript + vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1): dependencies: lightningcss: 1.32.0 @@ -7495,9 +7734,9 @@ snapshots: ws@8.21.1: optional: true - xsschema@0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3): + xsschema@0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3): optionalDependencies: - '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@6.0.3)) + '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@7.0.2)) effect: 3.22.0 sury: 10.0.4 zod: 4.4.3 From d965bbac08de08c13e1da8f80ad0048d0ccf7f29 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 15:08:04 +0200 Subject: [PATCH 02/17] test: split unit and integration test tiers Files using createTestDb/createTestRedis are renamed *.int.test.ts and run in test:integration (docker pg + redis); test:unit is the infra-free suite and drops to ~4s. A new oss-module-shape/int-test-file-naming lint rule keeps the split from rotting. vite-tsconfig-paths makes @openora/core/* resolve to src in core's suites, so vitest related/watch see cross-module edges. --- .oxlintrc.json | 1 + ...vice.test.ts => audit.service.int.test.ts} | 0 ...ice.test.ts => gaming.service.int.test.ts} | 0 ....cache.test.ts => lobby.cache.int.test.ts} | 0 ...ms.reads.test.ts => cms.reads.int.test.ts} | 0 ....router.test.ts => cms.router.int.test.ts} | 0 ...ervice.test.ts => cms.service.int.test.ts} | 0 ...test.ts => compliance.service.int.test.ts} | 0 ... => hmac-kyc-webhook-verifier.int.test.ts} | 0 ...r.test.ts => kyc-admin.router.int.test.ts} | 0 ...test.ts => kyc-webhook.router.int.test.ts} | 0 ...ervice.test.ts => kyc.service.int.test.ts} | 0 ...t.ts => rg-monitoring.service.int.test.ts} | 0 ...g.router.test.ts => rg.router.int.test.ts} | 0 ...service.test.ts => rg.service.int.test.ts} | 0 ...rvice.test.ts => chat.service.int.test.ts} | 0 ...t.ts => notifications.service.int.test.ts} | 0 ...ervice.test.ts => iam.service.int.test.ts} | 0 ...st.ts => admin-user-directory.int.test.ts} | 0 ...est.ts => identity.rate-limit.int.test.ts} | 0 ...e.test.ts => identity.service.int.test.ts} | 0 ... => login-enforcement.service.int.test.ts} | 0 ...est.ts => phone-login.service.int.test.ts} | 0 ....test.ts => kyc-status-writer.int.test.ts} | 0 ...uter.test.ts => player.router.int.test.ts} | 0 ...ice.test.ts => player.service.int.test.ts} | 0 ...ce.test.ts => profile.service.int.test.ts} | 0 ...est.ts => get-active-tag-keys.int.test.ts} | 0 ....ts => tag-evaluation.service.int.test.ts} | 0 ...e.test.ts => tag-rule.service.int.test.ts} | 0 ....router.test.ts => tag.router.int.test.ts} | 0 ...ervice.test.ts => tag.service.int.test.ts} | 0 ...-guard.test.ts => admin-guard.int.test.ts} | 0 ...r.test.ts => session-resolver.int.test.ts} | 0 .../{relay.test.ts => relay.int.test.ts} | 0 ...e.test.ts => bullmq-job-queue.int.test.ts} | 0 .../{cache.test.ts => cache.int.test.ts} | 0 ...vent-bus.test.ts => event-bus.int.test.ts} | 0 ...miter.test.ts => rate-limiter.int.test.ts} | 0 ...-cache.test.ts => redis-cache.int.test.ts} | 0 ...test.ts => redis-rate-limiter.int.test.ts} | 0 ...st.ts => redis-streams-broker.int.test.ts} | 0 ...ng.test.ts => admin-reporting.int.test.ts} | 0 ...ts => wallet-auto-rule.router.int.test.ts} | 0 ....ts => wallet-auto-withdrawal.int.test.ts} | 0 ...ts => wallet-commands.service.int.test.ts} | 0 ...te.test.ts => wallet-kyc-gate.int.test.ts} | 0 ...t.ts => wallet-webhook.router.int.test.ts} | 0 ....test.ts => wallet.rate-limit.int.test.ts} | 0 ...uter.test.ts => wallet.router.int.test.ts} | 0 ...ice.test.ts => wallet.service.int.test.ts} | 0 packages/core/vitest.config.ts | 4 ++- packages/core/vitest.integration.config.ts | 14 +++++++++ tools/lint/oxlint-module-shape-plugin.mjs | 31 +++++++++++++++++++ 54 files changed, 49 insertions(+), 1 deletion(-) rename packages/core/src/audit/__tests__/{audit.service.test.ts => audit.service.int.test.ts} (100%) rename packages/core/src/casino/gaming/__tests__/{gaming.service.test.ts => gaming.service.int.test.ts} (100%) rename packages/core/src/casino/lobby/__tests__/{lobby.cache.test.ts => lobby.cache.int.test.ts} (100%) rename packages/core/src/cms/__tests__/{cms.reads.test.ts => cms.reads.int.test.ts} (100%) rename packages/core/src/cms/__tests__/{cms.router.test.ts => cms.router.int.test.ts} (100%) rename packages/core/src/cms/__tests__/{cms.service.test.ts => cms.service.int.test.ts} (100%) rename packages/core/src/compliance/__tests__/{compliance.service.test.ts => compliance.service.int.test.ts} (100%) rename packages/core/src/compliance/__tests__/{hmac-kyc-webhook-verifier.test.ts => hmac-kyc-webhook-verifier.int.test.ts} (100%) rename packages/core/src/compliance/__tests__/{kyc-admin.router.test.ts => kyc-admin.router.int.test.ts} (100%) rename packages/core/src/compliance/__tests__/{kyc-webhook.router.test.ts => kyc-webhook.router.int.test.ts} (100%) rename packages/core/src/compliance/__tests__/{kyc.service.test.ts => kyc.service.int.test.ts} (100%) rename packages/core/src/compliance/__tests__/{rg-monitoring.service.test.ts => rg-monitoring.service.int.test.ts} (100%) rename packages/core/src/compliance/__tests__/{rg.router.test.ts => rg.router.int.test.ts} (100%) rename packages/core/src/compliance/__tests__/{rg.service.test.ts => rg.service.int.test.ts} (100%) rename packages/core/src/engagement/chat/__tests__/{chat.service.test.ts => chat.service.int.test.ts} (100%) rename packages/core/src/engagement/notifications/__tests__/{notifications.service.test.ts => notifications.service.int.test.ts} (100%) rename packages/core/src/iam/__tests__/{iam.service.test.ts => iam.service.int.test.ts} (100%) rename packages/core/src/pam/identity/__tests__/{admin-user-directory.test.ts => admin-user-directory.int.test.ts} (100%) rename packages/core/src/pam/identity/__tests__/{identity.rate-limit.test.ts => identity.rate-limit.int.test.ts} (100%) rename packages/core/src/pam/identity/__tests__/{identity.service.test.ts => identity.service.int.test.ts} (100%) rename packages/core/src/pam/identity/__tests__/{login-enforcement.service.test.ts => login-enforcement.service.int.test.ts} (100%) rename packages/core/src/pam/identity/__tests__/{phone-login.service.test.ts => phone-login.service.int.test.ts} (100%) rename packages/core/src/pam/player-management/__tests__/{kyc-status-writer.test.ts => kyc-status-writer.int.test.ts} (100%) rename packages/core/src/pam/player-management/__tests__/{player.router.test.ts => player.router.int.test.ts} (100%) rename packages/core/src/pam/player-management/__tests__/{player.service.test.ts => player.service.int.test.ts} (100%) rename packages/core/src/pam/profile/__tests__/{profile.service.test.ts => profile.service.int.test.ts} (100%) rename packages/core/src/pam/tag/__tests__/{get-active-tag-keys.test.ts => get-active-tag-keys.int.test.ts} (100%) rename packages/core/src/pam/tag/__tests__/{tag-evaluation.service.test.ts => tag-evaluation.service.int.test.ts} (100%) rename packages/core/src/pam/tag/__tests__/{tag-rule.service.test.ts => tag-rule.service.int.test.ts} (100%) rename packages/core/src/pam/tag/__tests__/{tag.router.test.ts => tag.router.int.test.ts} (100%) rename packages/core/src/pam/tag/__tests__/{tag.service.test.ts => tag.service.int.test.ts} (100%) rename packages/core/src/server/auth/__tests__/{admin-guard.test.ts => admin-guard.int.test.ts} (100%) rename packages/core/src/server/auth/__tests__/{session-resolver.test.ts => session-resolver.int.test.ts} (100%) rename packages/core/src/server/db/outbox/__tests__/{relay.test.ts => relay.int.test.ts} (100%) rename packages/core/src/server/kernel/__tests__/{bullmq-job-queue.test.ts => bullmq-job-queue.int.test.ts} (100%) rename packages/core/src/server/kernel/__tests__/{cache.test.ts => cache.int.test.ts} (100%) rename packages/core/src/server/kernel/__tests__/{event-bus.test.ts => event-bus.int.test.ts} (100%) rename packages/core/src/server/kernel/__tests__/{rate-limiter.test.ts => rate-limiter.int.test.ts} (100%) rename packages/core/src/server/kernel/__tests__/{redis-cache.test.ts => redis-cache.int.test.ts} (100%) rename packages/core/src/server/kernel/__tests__/{redis-rate-limiter.test.ts => redis-rate-limiter.int.test.ts} (100%) rename packages/core/src/server/kernel/__tests__/{redis-streams-broker.test.ts => redis-streams-broker.int.test.ts} (100%) rename packages/core/src/wallet/__tests__/{admin-reporting.test.ts => admin-reporting.int.test.ts} (100%) rename packages/core/src/wallet/__tests__/{wallet-auto-rule.router.test.ts => wallet-auto-rule.router.int.test.ts} (100%) rename packages/core/src/wallet/__tests__/{wallet-auto-withdrawal.test.ts => wallet-auto-withdrawal.int.test.ts} (100%) rename packages/core/src/wallet/__tests__/{wallet-commands.service.test.ts => wallet-commands.service.int.test.ts} (100%) rename packages/core/src/wallet/__tests__/{wallet-kyc-gate.test.ts => wallet-kyc-gate.int.test.ts} (100%) rename packages/core/src/wallet/__tests__/{wallet-webhook.router.test.ts => wallet-webhook.router.int.test.ts} (100%) rename packages/core/src/wallet/__tests__/{wallet.rate-limit.test.ts => wallet.rate-limit.int.test.ts} (100%) rename packages/core/src/wallet/__tests__/{wallet.router.test.ts => wallet.router.int.test.ts} (100%) rename packages/core/src/wallet/__tests__/{wallet.service.test.ts => wallet.service.int.test.ts} (100%) create mode 100644 packages/core/vitest.integration.config.ts diff --git a/.oxlintrc.json b/.oxlintrc.json index 5cce116a..c71aeb67 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -17,6 +17,7 @@ "oss-boundaries/no-engine-to-domain": "error", "oss-module-shape/module-file-placement": "error", "oss-module-shape/layer-file-naming": "error", + "oss-module-shape/int-test-file-naming": "error", "oss-module-shape/no-inline-pg-enum": "error", "oss-module-shape/no-relative-zone-escape": "error", "oss-module-shape/no-reinfer-imported-schema": "error", diff --git a/packages/core/src/audit/__tests__/audit.service.test.ts b/packages/core/src/audit/__tests__/audit.service.int.test.ts similarity index 100% rename from packages/core/src/audit/__tests__/audit.service.test.ts rename to packages/core/src/audit/__tests__/audit.service.int.test.ts diff --git a/packages/core/src/casino/gaming/__tests__/gaming.service.test.ts b/packages/core/src/casino/gaming/__tests__/gaming.service.int.test.ts similarity index 100% rename from packages/core/src/casino/gaming/__tests__/gaming.service.test.ts rename to packages/core/src/casino/gaming/__tests__/gaming.service.int.test.ts diff --git a/packages/core/src/casino/lobby/__tests__/lobby.cache.test.ts b/packages/core/src/casino/lobby/__tests__/lobby.cache.int.test.ts similarity index 100% rename from packages/core/src/casino/lobby/__tests__/lobby.cache.test.ts rename to packages/core/src/casino/lobby/__tests__/lobby.cache.int.test.ts diff --git a/packages/core/src/cms/__tests__/cms.reads.test.ts b/packages/core/src/cms/__tests__/cms.reads.int.test.ts similarity index 100% rename from packages/core/src/cms/__tests__/cms.reads.test.ts rename to packages/core/src/cms/__tests__/cms.reads.int.test.ts diff --git a/packages/core/src/cms/__tests__/cms.router.test.ts b/packages/core/src/cms/__tests__/cms.router.int.test.ts similarity index 100% rename from packages/core/src/cms/__tests__/cms.router.test.ts rename to packages/core/src/cms/__tests__/cms.router.int.test.ts diff --git a/packages/core/src/cms/__tests__/cms.service.test.ts b/packages/core/src/cms/__tests__/cms.service.int.test.ts similarity index 100% rename from packages/core/src/cms/__tests__/cms.service.test.ts rename to packages/core/src/cms/__tests__/cms.service.int.test.ts diff --git a/packages/core/src/compliance/__tests__/compliance.service.test.ts b/packages/core/src/compliance/__tests__/compliance.service.int.test.ts similarity index 100% rename from packages/core/src/compliance/__tests__/compliance.service.test.ts rename to packages/core/src/compliance/__tests__/compliance.service.int.test.ts diff --git a/packages/core/src/compliance/__tests__/hmac-kyc-webhook-verifier.test.ts b/packages/core/src/compliance/__tests__/hmac-kyc-webhook-verifier.int.test.ts similarity index 100% rename from packages/core/src/compliance/__tests__/hmac-kyc-webhook-verifier.test.ts rename to packages/core/src/compliance/__tests__/hmac-kyc-webhook-verifier.int.test.ts diff --git a/packages/core/src/compliance/__tests__/kyc-admin.router.test.ts b/packages/core/src/compliance/__tests__/kyc-admin.router.int.test.ts similarity index 100% rename from packages/core/src/compliance/__tests__/kyc-admin.router.test.ts rename to packages/core/src/compliance/__tests__/kyc-admin.router.int.test.ts diff --git a/packages/core/src/compliance/__tests__/kyc-webhook.router.test.ts b/packages/core/src/compliance/__tests__/kyc-webhook.router.int.test.ts similarity index 100% rename from packages/core/src/compliance/__tests__/kyc-webhook.router.test.ts rename to packages/core/src/compliance/__tests__/kyc-webhook.router.int.test.ts diff --git a/packages/core/src/compliance/__tests__/kyc.service.test.ts b/packages/core/src/compliance/__tests__/kyc.service.int.test.ts similarity index 100% rename from packages/core/src/compliance/__tests__/kyc.service.test.ts rename to packages/core/src/compliance/__tests__/kyc.service.int.test.ts diff --git a/packages/core/src/compliance/__tests__/rg-monitoring.service.test.ts b/packages/core/src/compliance/__tests__/rg-monitoring.service.int.test.ts similarity index 100% rename from packages/core/src/compliance/__tests__/rg-monitoring.service.test.ts rename to packages/core/src/compliance/__tests__/rg-monitoring.service.int.test.ts diff --git a/packages/core/src/compliance/__tests__/rg.router.test.ts b/packages/core/src/compliance/__tests__/rg.router.int.test.ts similarity index 100% rename from packages/core/src/compliance/__tests__/rg.router.test.ts rename to packages/core/src/compliance/__tests__/rg.router.int.test.ts diff --git a/packages/core/src/compliance/__tests__/rg.service.test.ts b/packages/core/src/compliance/__tests__/rg.service.int.test.ts similarity index 100% rename from packages/core/src/compliance/__tests__/rg.service.test.ts rename to packages/core/src/compliance/__tests__/rg.service.int.test.ts diff --git a/packages/core/src/engagement/chat/__tests__/chat.service.test.ts b/packages/core/src/engagement/chat/__tests__/chat.service.int.test.ts similarity index 100% rename from packages/core/src/engagement/chat/__tests__/chat.service.test.ts rename to packages/core/src/engagement/chat/__tests__/chat.service.int.test.ts diff --git a/packages/core/src/engagement/notifications/__tests__/notifications.service.test.ts b/packages/core/src/engagement/notifications/__tests__/notifications.service.int.test.ts similarity index 100% rename from packages/core/src/engagement/notifications/__tests__/notifications.service.test.ts rename to packages/core/src/engagement/notifications/__tests__/notifications.service.int.test.ts diff --git a/packages/core/src/iam/__tests__/iam.service.test.ts b/packages/core/src/iam/__tests__/iam.service.int.test.ts similarity index 100% rename from packages/core/src/iam/__tests__/iam.service.test.ts rename to packages/core/src/iam/__tests__/iam.service.int.test.ts diff --git a/packages/core/src/pam/identity/__tests__/admin-user-directory.test.ts b/packages/core/src/pam/identity/__tests__/admin-user-directory.int.test.ts similarity index 100% rename from packages/core/src/pam/identity/__tests__/admin-user-directory.test.ts rename to packages/core/src/pam/identity/__tests__/admin-user-directory.int.test.ts diff --git a/packages/core/src/pam/identity/__tests__/identity.rate-limit.test.ts b/packages/core/src/pam/identity/__tests__/identity.rate-limit.int.test.ts similarity index 100% rename from packages/core/src/pam/identity/__tests__/identity.rate-limit.test.ts rename to packages/core/src/pam/identity/__tests__/identity.rate-limit.int.test.ts diff --git a/packages/core/src/pam/identity/__tests__/identity.service.test.ts b/packages/core/src/pam/identity/__tests__/identity.service.int.test.ts similarity index 100% rename from packages/core/src/pam/identity/__tests__/identity.service.test.ts rename to packages/core/src/pam/identity/__tests__/identity.service.int.test.ts diff --git a/packages/core/src/pam/identity/__tests__/login-enforcement.service.test.ts b/packages/core/src/pam/identity/__tests__/login-enforcement.service.int.test.ts similarity index 100% rename from packages/core/src/pam/identity/__tests__/login-enforcement.service.test.ts rename to packages/core/src/pam/identity/__tests__/login-enforcement.service.int.test.ts diff --git a/packages/core/src/pam/identity/__tests__/phone-login.service.test.ts b/packages/core/src/pam/identity/__tests__/phone-login.service.int.test.ts similarity index 100% rename from packages/core/src/pam/identity/__tests__/phone-login.service.test.ts rename to packages/core/src/pam/identity/__tests__/phone-login.service.int.test.ts diff --git a/packages/core/src/pam/player-management/__tests__/kyc-status-writer.test.ts b/packages/core/src/pam/player-management/__tests__/kyc-status-writer.int.test.ts similarity index 100% rename from packages/core/src/pam/player-management/__tests__/kyc-status-writer.test.ts rename to packages/core/src/pam/player-management/__tests__/kyc-status-writer.int.test.ts diff --git a/packages/core/src/pam/player-management/__tests__/player.router.test.ts b/packages/core/src/pam/player-management/__tests__/player.router.int.test.ts similarity index 100% rename from packages/core/src/pam/player-management/__tests__/player.router.test.ts rename to packages/core/src/pam/player-management/__tests__/player.router.int.test.ts diff --git a/packages/core/src/pam/player-management/__tests__/player.service.test.ts b/packages/core/src/pam/player-management/__tests__/player.service.int.test.ts similarity index 100% rename from packages/core/src/pam/player-management/__tests__/player.service.test.ts rename to packages/core/src/pam/player-management/__tests__/player.service.int.test.ts diff --git a/packages/core/src/pam/profile/__tests__/profile.service.test.ts b/packages/core/src/pam/profile/__tests__/profile.service.int.test.ts similarity index 100% rename from packages/core/src/pam/profile/__tests__/profile.service.test.ts rename to packages/core/src/pam/profile/__tests__/profile.service.int.test.ts diff --git a/packages/core/src/pam/tag/__tests__/get-active-tag-keys.test.ts b/packages/core/src/pam/tag/__tests__/get-active-tag-keys.int.test.ts similarity index 100% rename from packages/core/src/pam/tag/__tests__/get-active-tag-keys.test.ts rename to packages/core/src/pam/tag/__tests__/get-active-tag-keys.int.test.ts diff --git a/packages/core/src/pam/tag/__tests__/tag-evaluation.service.test.ts b/packages/core/src/pam/tag/__tests__/tag-evaluation.service.int.test.ts similarity index 100% rename from packages/core/src/pam/tag/__tests__/tag-evaluation.service.test.ts rename to packages/core/src/pam/tag/__tests__/tag-evaluation.service.int.test.ts diff --git a/packages/core/src/pam/tag/__tests__/tag-rule.service.test.ts b/packages/core/src/pam/tag/__tests__/tag-rule.service.int.test.ts similarity index 100% rename from packages/core/src/pam/tag/__tests__/tag-rule.service.test.ts rename to packages/core/src/pam/tag/__tests__/tag-rule.service.int.test.ts diff --git a/packages/core/src/pam/tag/__tests__/tag.router.test.ts b/packages/core/src/pam/tag/__tests__/tag.router.int.test.ts similarity index 100% rename from packages/core/src/pam/tag/__tests__/tag.router.test.ts rename to packages/core/src/pam/tag/__tests__/tag.router.int.test.ts diff --git a/packages/core/src/pam/tag/__tests__/tag.service.test.ts b/packages/core/src/pam/tag/__tests__/tag.service.int.test.ts similarity index 100% rename from packages/core/src/pam/tag/__tests__/tag.service.test.ts rename to packages/core/src/pam/tag/__tests__/tag.service.int.test.ts diff --git a/packages/core/src/server/auth/__tests__/admin-guard.test.ts b/packages/core/src/server/auth/__tests__/admin-guard.int.test.ts similarity index 100% rename from packages/core/src/server/auth/__tests__/admin-guard.test.ts rename to packages/core/src/server/auth/__tests__/admin-guard.int.test.ts diff --git a/packages/core/src/server/auth/__tests__/session-resolver.test.ts b/packages/core/src/server/auth/__tests__/session-resolver.int.test.ts similarity index 100% rename from packages/core/src/server/auth/__tests__/session-resolver.test.ts rename to packages/core/src/server/auth/__tests__/session-resolver.int.test.ts diff --git a/packages/core/src/server/db/outbox/__tests__/relay.test.ts b/packages/core/src/server/db/outbox/__tests__/relay.int.test.ts similarity index 100% rename from packages/core/src/server/db/outbox/__tests__/relay.test.ts rename to packages/core/src/server/db/outbox/__tests__/relay.int.test.ts diff --git a/packages/core/src/server/kernel/__tests__/bullmq-job-queue.test.ts b/packages/core/src/server/kernel/__tests__/bullmq-job-queue.int.test.ts similarity index 100% rename from packages/core/src/server/kernel/__tests__/bullmq-job-queue.test.ts rename to packages/core/src/server/kernel/__tests__/bullmq-job-queue.int.test.ts diff --git a/packages/core/src/server/kernel/__tests__/cache.test.ts b/packages/core/src/server/kernel/__tests__/cache.int.test.ts similarity index 100% rename from packages/core/src/server/kernel/__tests__/cache.test.ts rename to packages/core/src/server/kernel/__tests__/cache.int.test.ts diff --git a/packages/core/src/server/kernel/__tests__/event-bus.test.ts b/packages/core/src/server/kernel/__tests__/event-bus.int.test.ts similarity index 100% rename from packages/core/src/server/kernel/__tests__/event-bus.test.ts rename to packages/core/src/server/kernel/__tests__/event-bus.int.test.ts diff --git a/packages/core/src/server/kernel/__tests__/rate-limiter.test.ts b/packages/core/src/server/kernel/__tests__/rate-limiter.int.test.ts similarity index 100% rename from packages/core/src/server/kernel/__tests__/rate-limiter.test.ts rename to packages/core/src/server/kernel/__tests__/rate-limiter.int.test.ts diff --git a/packages/core/src/server/kernel/__tests__/redis-cache.test.ts b/packages/core/src/server/kernel/__tests__/redis-cache.int.test.ts similarity index 100% rename from packages/core/src/server/kernel/__tests__/redis-cache.test.ts rename to packages/core/src/server/kernel/__tests__/redis-cache.int.test.ts diff --git a/packages/core/src/server/kernel/__tests__/redis-rate-limiter.test.ts b/packages/core/src/server/kernel/__tests__/redis-rate-limiter.int.test.ts similarity index 100% rename from packages/core/src/server/kernel/__tests__/redis-rate-limiter.test.ts rename to packages/core/src/server/kernel/__tests__/redis-rate-limiter.int.test.ts diff --git a/packages/core/src/server/kernel/__tests__/redis-streams-broker.test.ts b/packages/core/src/server/kernel/__tests__/redis-streams-broker.int.test.ts similarity index 100% rename from packages/core/src/server/kernel/__tests__/redis-streams-broker.test.ts rename to packages/core/src/server/kernel/__tests__/redis-streams-broker.int.test.ts diff --git a/packages/core/src/wallet/__tests__/admin-reporting.test.ts b/packages/core/src/wallet/__tests__/admin-reporting.int.test.ts similarity index 100% rename from packages/core/src/wallet/__tests__/admin-reporting.test.ts rename to packages/core/src/wallet/__tests__/admin-reporting.int.test.ts diff --git a/packages/core/src/wallet/__tests__/wallet-auto-rule.router.test.ts b/packages/core/src/wallet/__tests__/wallet-auto-rule.router.int.test.ts similarity index 100% rename from packages/core/src/wallet/__tests__/wallet-auto-rule.router.test.ts rename to packages/core/src/wallet/__tests__/wallet-auto-rule.router.int.test.ts diff --git a/packages/core/src/wallet/__tests__/wallet-auto-withdrawal.test.ts b/packages/core/src/wallet/__tests__/wallet-auto-withdrawal.int.test.ts similarity index 100% rename from packages/core/src/wallet/__tests__/wallet-auto-withdrawal.test.ts rename to packages/core/src/wallet/__tests__/wallet-auto-withdrawal.int.test.ts diff --git a/packages/core/src/wallet/__tests__/wallet-commands.service.test.ts b/packages/core/src/wallet/__tests__/wallet-commands.service.int.test.ts similarity index 100% rename from packages/core/src/wallet/__tests__/wallet-commands.service.test.ts rename to packages/core/src/wallet/__tests__/wallet-commands.service.int.test.ts diff --git a/packages/core/src/wallet/__tests__/wallet-kyc-gate.test.ts b/packages/core/src/wallet/__tests__/wallet-kyc-gate.int.test.ts similarity index 100% rename from packages/core/src/wallet/__tests__/wallet-kyc-gate.test.ts rename to packages/core/src/wallet/__tests__/wallet-kyc-gate.int.test.ts diff --git a/packages/core/src/wallet/__tests__/wallet-webhook.router.test.ts b/packages/core/src/wallet/__tests__/wallet-webhook.router.int.test.ts similarity index 100% rename from packages/core/src/wallet/__tests__/wallet-webhook.router.test.ts rename to packages/core/src/wallet/__tests__/wallet-webhook.router.int.test.ts diff --git a/packages/core/src/wallet/__tests__/wallet.rate-limit.test.ts b/packages/core/src/wallet/__tests__/wallet.rate-limit.int.test.ts similarity index 100% rename from packages/core/src/wallet/__tests__/wallet.rate-limit.test.ts rename to packages/core/src/wallet/__tests__/wallet.rate-limit.int.test.ts diff --git a/packages/core/src/wallet/__tests__/wallet.router.test.ts b/packages/core/src/wallet/__tests__/wallet.router.int.test.ts similarity index 100% rename from packages/core/src/wallet/__tests__/wallet.router.test.ts rename to packages/core/src/wallet/__tests__/wallet.router.int.test.ts diff --git a/packages/core/src/wallet/__tests__/wallet.service.test.ts b/packages/core/src/wallet/__tests__/wallet.service.int.test.ts similarity index 100% rename from packages/core/src/wallet/__tests__/wallet.service.test.ts rename to packages/core/src/wallet/__tests__/wallet.service.int.test.ts diff --git a/packages/core/vitest.config.ts b/packages/core/vitest.config.ts index 69a9ec9d..d24e8820 100644 --- a/packages/core/vitest.config.ts +++ b/packages/core/vitest.config.ts @@ -1,10 +1,12 @@ import { defineConfig } from 'vitest/config'; +import tsconfigPaths from 'vite-tsconfig-paths'; export default defineConfig({ + plugins: [tsconfigPaths()], test: { globals: true, environment: 'node', include: ['src/**/*.{test,spec}.ts'], - exclude: ['dist/**', 'node_modules/**'], + exclude: ['dist/**', 'node_modules/**', 'src/**/*.int.{test,spec}.ts'], }, }); diff --git a/packages/core/vitest.integration.config.ts b/packages/core/vitest.integration.config.ts new file mode 100644 index 00000000..9927826f --- /dev/null +++ b/packages/core/vitest.integration.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from 'vitest/config'; +import tsconfigPaths from 'vite-tsconfig-paths'; + +export default defineConfig({ + plugins: [tsconfigPaths()], + test: { + globals: true, + environment: 'node', + include: ['src/**/*.int.{test,spec}.ts'], + exclude: ['dist/**', 'node_modules/**'], + testTimeout: 30_000, + hookTimeout: 30_000, + }, +}); diff --git a/tools/lint/oxlint-module-shape-plugin.mjs b/tools/lint/oxlint-module-shape-plugin.mjs index 64407564..af5af04b 100644 --- a/tools/lint/oxlint-module-shape-plugin.mjs +++ b/tools/lint/oxlint-module-shape-plugin.mjs @@ -13,6 +13,9 @@ // current tree. // layer-file-naming - files directly in service/ end .service.ts (plus a short surveyed // allowlist of pre-existing exceptions); files in __tests__/ end .test.ts. +// int-test-file-naming - a test file importing createTestDb/createTestRedis must end +// .int.test.ts, so `test:unit` stays the infra-free suite and `test:integration` owns +// everything that needs docker Postgres + Redis. // no-inline-pg-enum - pgEnum('name', [...]) with an inline array literal is an error; // values must come from a named tuple (see packages/core/src/wallet/schema/index.ts). // no-relative-zone-escape - a relative import that leaves its module root must instead use @@ -775,11 +778,39 @@ const noInlineZEnumOutsideContract = { }, }; +const REAL_INFRA_FACTORIES = new Set(['createTestDb', 'createTestRedis']); + +const intTestFileNaming = { + create(context) { + const file = filename(context); + if (!file.endsWith('.test.ts') || file.endsWith('.int.test.ts')) { + return {}; + } + return { + ImportDeclaration(node) { + const usesRealInfra = node.specifiers.some( + (s) => s.imported && REAL_INFRA_FACTORIES.has(s.imported.name), + ); + if (!usesRealInfra) { + return; + } + context.report({ + node, + message: + `${file.split('/').pop()} imports ${[...REAL_INFRA_FACTORIES].join('/')} but is not ` + + 'named *.int.test.ts - rename it so it runs in test:integration, not test:unit.', + }); + }, + }; + }, +}; + export default { meta: { name: 'oss-module-shape' }, rules: { 'module-file-placement': moduleFilePlacement, 'layer-file-naming': layerFileNaming, + 'int-test-file-naming': intTestFileNaming, 'no-inline-pg-enum': noInlinePgEnum, 'no-relative-zone-escape': noRelativeZoneEscape, 'no-reinfer-imported-schema': noReinferImportedSchema, From 24f3228924c42745eb422909ea086fe0e80cef24 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 15:08:57 +0200 Subject: [PATCH 03/17] fix(tooling): keep generated migrations out of the newline hook Appending a final newline changes a migration file's bytes, and drizzle hashes those bytes to decide what is already applied - an applied migration then re-runs and fails. db:setup:test gains a --fresh flag to recover a local test db that already recorded a stale hash. --- tools/db/setup-test-db.ts | 13 +++++++++++-- tools/lint/ensure-final-newline.mjs | 5 ++++- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/tools/db/setup-test-db.ts b/tools/db/setup-test-db.ts index 65590768..5e9ec75a 100644 --- a/tools/db/setup-test-db.ts +++ b/tools/db/setup-test-db.ts @@ -5,6 +5,8 @@ const TEST_URL = process.env['TEST_DATABASE_URL'] ?? 'postgres://postgres:postgres@localhost:5432/oss_igaming_test'; +const isFresh = process.argv.includes('--fresh'); + async function main() { const url = new URL(TEST_URL); const dbName = url.pathname.replace(/^\//, ''); @@ -20,8 +22,15 @@ async function main() { try { const existing = await client.query('SELECT 1 FROM pg_database WHERE datname = $1', [dbName]); if (existing.rowCount && existing.rowCount > 0) { - console.log(`Test database "${dbName}" already exists.`); - return; + if (!isFresh) { + console.log( + `Test database "${dbName}" already exists. Re-run with --fresh if a migration was ` + + 'edited after it was applied here.', + ); + return; + } + await client.query(`DROP DATABASE "${dbName.replace(/"/g, '')}" WITH (FORCE)`); + console.log(`Dropped test database "${dbName}".`); } // CREATE DATABASE cannot be parameterized; dbName comes from our own env. await client.query(`CREATE DATABASE "${dbName.replace(/"/g, '')}"`); diff --git a/tools/lint/ensure-final-newline.mjs b/tools/lint/ensure-final-newline.mjs index 00d361f0..070f697c 100644 --- a/tools/lint/ensure-final-newline.mjs +++ b/tools/lint/ensure-final-newline.mjs @@ -11,6 +11,9 @@ import { fileURLToPath } from 'node:url'; const root = resolve(dirname(fileURLToPath(import.meta.url)), '../..'); const SKIP = new Set(['pnpm-lock.yaml']); +// Drizzle hashes each migration file's exact bytes to decide what is already applied, +// so appending a newline makes an applied migration re-run and fail (ADR-0027). +const SKIP_PATTERNS = [/\/drizzle\/migrations\/.*\.sql$/]; const files = execSync('git ls-files -z', { cwd: root, maxBuffer: 64 * 1024 * 1024 }) .toString('utf8') @@ -19,7 +22,7 @@ const files = execSync('git ls-files -z', { cwd: root, maxBuffer: 64 * 1024 * 10 let fixed = 0; for (const rel of files) { - if (SKIP.has(rel)) { + if (SKIP.has(rel) || SKIP_PATTERNS.some((p) => p.test(rel))) { continue; } const path = join(root, rel); From 0488f1d840389f80685aaa713b526b3288230c11 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 15:09:55 +0200 Subject: [PATCH 04/17] docs(rules): split the code standard into always-on core plus on-demand detail conventions.md drops from 236 to ~60 lines - philosophy, a hard Never list, a hard Always list, and a table routing to the file that covers the change. clean-architecture.md folds into docs/standards/module-structure.md. The nine docs/standards files live outside .rulesync, so they are read on demand instead of being injected into every agent context. --- .rulesync/rules/conventions.md | 265 +++--------------- .rulesync/rules/db-conventions.md | 4 +- .rulesync/rules/overview.md | 10 +- .rulesync/skills/add-feature/SKILL.md | 2 +- .rulesync/skills/create-pr/SKILL.md | 2 +- .rulesync/skills/enhance-prompt/SKILL.md | 2 +- .rulesync/skills/oss-review/SKILL.md | 2 +- .rulesync/subagents/contract-reviewer.md | 2 +- .rulesync/subagents/dev.md | 2 +- .rulesync/subagents/module-author.md | 2 +- .rulesync/subagents/quality-reviewer.md | 4 +- docs/standards/comments.md | 20 ++ docs/standards/enforcement.md | 11 + docs/standards/errors.md | 46 +++ docs/standards/functions.md | 87 ++++++ docs/standards/git-delivery.md | 11 + .../standards/module-structure.md | 67 +++-- docs/standards/react-sdk.md | 32 +++ docs/standards/testing.md | 39 +++ docs/standards/types.md | 42 +++ .../fixtures/test-kyc-config-plugin.ts | 2 +- tools/lint/oxlint-boundaries-plugin.mjs | 2 +- 22 files changed, 403 insertions(+), 253 deletions(-) create mode 100644 docs/standards/comments.md create mode 100644 docs/standards/enforcement.md create mode 100644 docs/standards/errors.md create mode 100644 docs/standards/functions.md create mode 100644 docs/standards/git-delivery.md rename .rulesync/rules/clean-architecture.md => docs/standards/module-structure.md (78%) create mode 100644 docs/standards/react-sdk.md create mode 100644 docs/standards/testing.md create mode 100644 docs/standards/types.md diff --git a/.rulesync/rules/conventions.md b/.rulesync/rules/conventions.md index cb9f8d2e..cea89a36 100644 --- a/.rulesync/rules/conventions.md +++ b/.rulesync/rules/conventions.md @@ -11,225 +11,52 @@ description: Engineering code conventions (TS, headless backend) - apply to ever # Engineering Conventions -The portable code standard - stack-agnostic principles, then TS/backend specifics. Structure/DI/boundaries: `clean-architecture`. Async seams: `messaging-and-microservices`. OSS naming, forbidden patterns, decision tree: `overview`. Keep this file portable (shared with the consumer repo). - -Goal: code that is clean, separated, scalable, and extendible - easy to understand and cheap to change. Examples show `// bad` (the smell) vs `// good` (the convention). - -## 1. Philosophy - -- **Functional and declarative by default.** Pure functions, immutable data, composition over imperative mutation and stateful classes. `orders.map((o) => o.amount * o.qty)`, not a `for` loop pushing into an accumulator. -- **Explicit over magic.** No auto-discovery, no decorator/reflection soup. Every wiring point is a greppable, typed call: `ctx.provide(SEND_EMAIL, () => new SmtpMailer(env.SMTP_URL))`, never `@Injectable()`. -- **Self-documenting.** Clear names beat comments: `percentChange`, not `d` + a comment. +The always-on core of the code standard: what you must obey while typing. Detail, examples and rationale live in `docs/standards/` - read the one file that matches the change instead of carrying all of it. Async seams: `messaging-and-microservices`. SQL / Drizzle: `db-conventions`. Repo map, decision tree, dependency rules: `overview`. + +| Change you are making | Read first | +| ------------------------------------- | ------------------------------------ | +| schema, type, enum-like value set | `docs/standards/types.md` | +| function, service method, constructor | `docs/standards/functions.md` | +| new module, DI wiring, integration | `docs/standards/module-structure.md` | +| error class, catch, money path | `docs/standards/errors.md` | +| a test | `docs/standards/testing.md` | +| a comment or JSDoc | `docs/standards/comments.md` | +| a hook / the typed client | `docs/standards/react-sdk.md` | +| commit, PR | `docs/standards/git-delivery.md` | +| a failing gate, a new lint rule | `docs/standards/enforcement.md` | + +## Philosophy + +- **Functional and declarative by default.** Pure functions, immutable data, composition over imperative mutation and stateful classes. +- **Explicit over magic.** No auto-discovery, no decorator/reflection soup; every wiring point is a greppable, typed call. +- **Self-documenting.** Clear names beat comments. - **Small and composable.** One concept per file; `parseUser()` + `sendWelcomeEmail()`, not `parseUserAndSendEmail()`. -- **YAGNI + DRY, in that order.** Don't build for imagined futures; abstract on the third occurrence, not the first. `settleBet(bet)`, not a one-caller `process(config, strategy, adapters, plugins)` engine. +- **YAGNI + DRY, in that order.** Abstract on the third occurrence, not the first. - **Boring and consistent.** Match the surrounding code's idiom, naming, and density. -## 2. Naming - -- Files `kebab-case.ts`, one concept per file, filename names the concept: `wallet.service.ts`, `format-money.ts` - never `helpers.ts`, `utils2.ts`. -- Types `PascalCase`; values/functions `camelCase`; true global constants `SCREAMING_SNAKE_CASE`. -- Schemas `Schema`; the inferred type is the bare ``. -- Booleans read as predicates (`isActive`, `canEdit`, `hasLoaded`); IO functions as verbs (`fetchInvoice`). -- Names carry units/intent: `delayMs`, `maxRetries` - never bare `delay`, `max`. Money is the one exception to "unit in the name": it's a decimal string + a `currency` field alongside (`amount`/`balance`/`threshold`, never `amountCents` - see `db-conventions` > Money, ADR-0029). - -## 3. Types and data modeling - -- **One source of truth per shape - infer, never hand-write.** `z.infer`, `typeof users.$inferSelect`, `Omit`. -- **Schema-first at every boundary** (HTTP, config, env, messages, events). Validate once at the edge, trust the type after. oRPC + Zod does this for routes; do the same for config/env/event payloads. -- **No `any`, anywhere - tests included.** `unknown` + narrowing, or the real type. Never hand-roll a duplicate of a type that's one import away (`$inferSelect`/`$inferInsert`, a shared `@openora/core/*` type) - it silently drifts from the source of truth. -- **Never re-infer an imported schema** - the type is exported once from the owning contract; consumers import it, never re-run `z.infer` (lint: `oss-module-shape/no-reinfer-imported-schema`). - ```ts - export type PlayerProfile = z.infer; // bad - import type { Player } from '@openora/core/contracts'; // good - ``` -- **No type casts (`as`) to silence the compiler - fix the root cause.** (`as const` is fine.) `as unknown as X` turns type-checking off entirely. If a symbol needs a type, give it one at the source (`createToken()`), don't cast at use sites. Exactly two sanctioned exceptions: - 1. **Test doubles** - route through the `mock` helper (`packages/core/src/testing/mock.ts`) so the cast lives in one audited place, never inline in a test. - 2. **Third-party type-inference boundaries** a library gives you no honest way to satisfy - one cast with a one-line `// Library boundary:` note. - - ```ts - // bad - const user = data as User; - const directory = { lookupPlayers } as unknown as AdminUserDirectory; - // good - const user = UserSchema.parse(data); - const TOKEN = createToken('CONFIG'); - const directory = mock({ lookupPlayers }); // cast confined to the helper - ``` - -- **Never `!` non-null assertions** - narrow or restructure instead (lint: `typescript/no-non-null-assertion`). - ```ts - return toDto(row!); // bad - return toDto(findOneOrThrow(rows, new XNotFoundError(id))); // good - ``` -- **Under `noUncheckedIndexedAccess`: `.at()`, destructuring-with-defaults, or an explicit guard - never `arr[i]!`.** `const [first = ''] = parts;` -- **`type` over `interface`** (lint-enforced). -- **Type entity ids through their owning type**: `User['id']`, never a bare `string` - the id's representation lives in one place and every signature follows when it changes (lint: `oss-module-shape/no-bare-string-id-param`). Bad: `roleId: string`. Good: `roleId: AdminRole['id']`. -- **Derive related schemas, never re-type fields**: `UserSchema.partial().omit({ id: true })`. -- **Enum-like value sets are a values + schema + type triple, declared once on the contract surface**: `X_STATUSES = [...] as const` -> `XStatusSchema = z.enum(X_STATUSES)` -> inferred `XStatus`. Anything in a route contract is public API - consumers need the runtime values (dropdowns, badge maps), so types alone are not enough. Never TS `enum`; never a second hand-typed copy of the set (in a consumer either - import it). Inline `z.enum([...])` outside a contract dir is a lint error (`oss-module-shape/no-inline-z-enum-outside-contract`). -- **Reuse `UuidSchema`** (`@openora/core/contracts`) for uuid fields, never a raw `z.uuid()`/`z.string().uuid()` - one source of truth for the shape (lint: `oss-module-shape/no-raw-z-uuid`). -- **Make illegal states unrepresentable** - discriminated unions (`{ status: 'loading' } | { status: 'ok'; data } | { status: 'error'; error }`) over optional-flag soup (`{ loading: boolean; data?: T; error?: Error }`). - -## 4. Functions, modules, and classes - -- **Pure functions with dependencies passed in; side effects at the edges.** `price(cart, rate)` - the caller owns the IO, not `price(cart)` reading a rate inside. -- **Immutability - derive, don't mutate.** `{ ...user, roles: [...user.roles, 'admin'] }`, not `user.roles.push('admin')`. -- **Construct objects by spread + override, never hand-copy field-by-field.** When a new object mostly mirrors an existing one (a DB insert from a validated input, a patch, a re-shaped payload, a row -> DTO), spread the source and set only what differs - `db.insert(x).values({ ...input, id, createdAt, hash })` - never re-list `field: input.field` per key. The hand-written copy is pure noise that silently drifts the moment a field is added on one side. For a row -> DTO that only turns `Date`/`Decimal` into strings, use `serializeRow(row, { dateFields, decimalFields })`, not a manual map. Three cases keep fields explicit, and only these: (1) **order-sensitive serialization** - a hash/signature canonical form ties its bytes to key order, so list keys by hand and treat the list as append-only; (2) the source carries **fields the target must not receive** - spread then `.omit`/destructure them off, or list explicitly; (3) **null vs undefined matters at a boundary that won't coerce** - normalize once (`actorId: input.actorId ?? null`) rather than relying on a spread passing `undefined` through. - - ```ts - // bad - re-lists every field; adding `userAgent` to the schema silently drops it here - .values({ id, actorId: input.actorId ?? null, actorType: input.actorType, action: input.action }) - // good - spread the validated input, override only the server-computed fields - .values({ ...input, createdAt: input.createdAt.toISOString() }) - ``` - -- **A `class` is only a thin dependency-holding shell at a composition root; methods delegate to pure functions.** -- **No inheritance for reuse - compose.** No decorators anywhere. -- **Short, single-purpose functions.** If you'd comment "// step 2" inside a function, extract it. -- **Guard clauses first, main path last.** Early-return the edge/simple cases up top; keep the biggest branch as the final unguarded return - flatter and easier to read than wrapping it in an `if`. - ```ts - // bad - main branch nested in an if - if (Array.isArray(v)) { - return v.map(f); - } - if (v !== null && typeof v === 'object') { - return heavy(v); - } - return v; - // good - edge cases guarded, heavy branch falls through to the end - if (Array.isArray(v)) { - return v.map(f); - } - if (v === null || typeof v !== 'object') { - return v; - } - return heavy(v); - ``` -- **Always brace control statements** - every `if`/`else`/`for`/`while` body in `{ }`, even one-liners (lint: `curly`). - ```ts - // bad - if (isActive) return true; - // good - if (isActive) { - return true; - } - ``` -- **Don't annotate a return type TypeScript can infer** - the body is the single source of truth. Annotate ONLY when: - - inference can't (recursion), or - - you deliberately widen/narrow, or - - it's a **published SDK export with no re-checking seam** - `/react` hooks + the typed client + plain-value `/server` helpers - where the explicit type IS the public contract (an inferred return silently leaks a refactor as a downstream breaking change). - - Routers, service methods, contracts, schemas, and plugins stay inferred: a seam re-checks them (oRPC validates handler output against the contract) or the type is an unspellable oRPC/Drizzle structure. Argument types always explicit. - -- **More than 3 parameters -> a single named-object param.** Four or more positionals are unreadable at the call site (`f(txn, ns, id, key, amt, cur, vals)` - which string is which?) and silently break when two share a type. Pass one object and destructure; a leading handle like a `tx`/`trx` may stay positional. Applies to functions, service methods, and constructors alike. - - ```ts - // bad - 7 positionals, three interchangeable strings - insertIdempotentTransaction( - txn, - namespace, - walletId, - rawIdempotencyKey, - amount, - currency, - values, - ); - // good - named object, self-documenting call site - insertIdempotentTransaction(txn, { - namespace, - walletId, - rawIdempotencyKey, - amount, - currency, - values, - }); - ``` - -- **Named exports only - no default exports.** Two sanctioned exceptions: `plugin.ts` (the loader reads `mod.default`, `load-plugins.ts:85`) and `drizzle.config.ts` (drizzle-kit requires it). - -## 5. Comments and documentation - -- **Zero comments. A comment is an exception you must justify, not a nicety.** Assume the answer is "no comment" and let the code carry the meaning. -- **The only thing that earns one: a fact the code CANNOT contain** - an external system's behaviour, a third-party bug, a spec/regulatory constraint. The test is whether a careful reader would otherwise "fix" the code and break it. `// Stripe rounds half-to-even; mirror it so our totals reconcile.` -- **A reason is not a fact - it does not earn a comment.** Why this order, why 2 retries and not 4, why not the obvious approach, what a block does, what changed: all of that goes in the commit message, the PR description, or an ADR. Those are versioned and reviewed; an inline rationale is neither, and it rots in place. Naming the thing well (`PLAYER_FACING_TIMEOUT_MS`) beats a paragraph above it. -- **If a block needs a comment to be understood, rename or extract first** - a comment is the fallback after that fails, never the first move. Writing one is the signal that the naming or the decomposition is wrong. -- **Never in tests.** A test name states the behaviour and the assertions state the evidence. Seeded values, fixture choices and timing tricks get named constants or helpers, not narration. -- **Same bar in config, CI and infra files** (`turbo.json`, workflow YAML, compose). Step names and keys are self-describing; step ordering and tuning rationale belong in the commit that introduced them. -- **Never** restate a name (`// increment the counter`), narrate steps (`// step 2`), announce edits (`// added for X`), or divide sections (`// ---`, `// ===`). -- **JSDoc on every exported function/class >~15 lines or with non-obvious params.** Multiline `/** ... */` block (opening and closing on their own lines). Document the surprising contract, not the name. -- **`// TODO:` for deferred work, `// FIXME:` for known-broken code** - greppable, with context and an issue key where one exists. Never bare. - ```ts - // TODO: replace polling with the webhook once BE ships it (ABC-312) - // FIXME: race - two admins approving the same withdrawal double-credit the player - ``` -- **`// mock:` marks placeholder data / stubbed behavior** so throwaway code stays findable. `// mock: fixed rate until the FX adapter lands` - -## 6. Structure and boundaries - -(The enforced import graph lives in `overview` > Dependency rules and `clean-architecture`.) - -- **Never import another module's internals.** Cross-module needs go through a command port, a domain event, or a shared contract. -- **Dependency direction points inward:** router -> service -> contracts; engine never imports a domain. -- **Public API is the package/subpath entry**, never a deep `dist/`/`src/` path. -- **No deep (`../../`+) relative import that leaves your own zone/module** - a `..` crossing into another domain, slice, or engine zone is a bug; use the `@openora/core/*` subpath. Relative paths stay inside your own dir (`./x`, `../schema/index.js`). Lint: `oss-module-shape/no-relative-zone-escape`. -- **No import cycles** - extract a shared module or move the type to contracts. - -## 7. React SDK (`@openora/core/react` + domain `react/` dirs) - -Headless repo - only the SDK consumption layer (hooks, typed client, auth, realtime) lives here, no UI. - -- **One `useX` per concern, returning a plain object** (`{ wallet, isLoading }`). -- **Hand-write `useMemo`/`useCallback` wherever a returned value/function is part of a hook's stability contract** - the OPPOSITE of the consumer-app rule, because the consumer's React Compiler does not reprocess pre-built `node_modules`. Keep hooks Rules-of-React compliant so the consumer's compiler can optimize callers. -- **Server state is not client state** - key/cache/invalidate via the query lib, never a raw `useEffect(fetch)`. - -## 8. State and side effects - -- **Side effects at the edges** (services, adapters, plugins, handlers), never in pure helpers. Events emit from the service after the DB commit. -- **Server state is owned by the data layer** - no ad-hoc shadow caches. - -## 9. Error handling - -- **Fail fast at boundaries with typed errors** - `Schema.parse(raw)` throws early. -- **No silent catches** - log with context and rethrow, or handle explicitly. -- **Typed, named error classes mapped to transport at the edge.** Use the shared factories (`makeNotFoundError`/`makeOwnershipError`/`makeConflictError`); the router's `mapErrors` keys off the exported class. `export const WalletNotFoundError = makeNotFoundError('Wallet');` -- **`ORPCError.message` is an English fallback for logs/tooling, not player-facing copy** - never render it directly in a UI. Consumers key UI copy off `.code` (the error category) plus typed `.data` fields (e.g. a `reason` discriminator); add a new field to `data`, not to `message`, when a UI needs to tell cases apart. -- **Money / critical paths are transactional and idempotent** - a DB guard inside the transaction, not just an `idempotencyKey` (delivery is at-least-once): - ```ts - await db.transaction(async (t) => { - if (await ledgerExists(t, idempotencyKey)) return; - await insertLedger(t, { idempotencyKey, amount }); - }); - ``` - -## 10. Testing - -- **Co-locate as `__tests__/.test.ts` (Vitest).** -- **Anything that touches the database is tested against real Postgres** - `createTestDb([migrate])` (`@openora/core/testing`) gives the file its own ephemeral database; `createTestRedis()` gives it a per-worker Redis logical DB. Never fake a query builder: a mocked chain proves a call order, not a result, so it survives the regressions that matter (unique-index dedupe, `FOR UPDATE` under concurrency, conditional atomic updates, cache invalidation). -- **What stays mocked:** external vendors (PSP, KYC, email, SMS, better-auth) and cross-module ports (`WALLET_COMMANDS`, `IdentityReader`, `EventBus`, `Logger`) - via the shared doubles in `packages/core/src/testing/mock.ts` (`mock`, `makeEventBus`, `makeAuditWriter`, `makeAdminGuard`, `testContext`), never a hand-rolled one in the test file. Engine (`server/**`) tests cannot import a domain schema (ADR-0024/0025), so they use the in-process implementations re-exported from `@openora/core/testing`. -- **Test behaviour, not implementation** - assert the resulting rows, cache state, and return value, not that a builder method was called: `expect(await service.get(id)).toEqual(user)`, not `expect(db.select).toHaveBeenCalled()`. -- **Cover new logic as part of the change** - unit for pure fns; always include authz negatives. -- **Deterministic and isolated:** no shared mutable state, no real network, seedable data. Real-infra suites stay parallel-safe - own your database and Redis keys, never assume an empty shared one. - -## 11. Dependencies - -- **Pin exact versions** (no `^`/`~`). -- **No inline `fetch`/`axios` in module code** - third-party integrations are ports + adapters bound at the root. -- **Add a dependency deliberately** - prefer stdlib/small packages; a few lines often beat a tree. - -## 12. Git and delivery - -- **Conventional commits, enforced** (commitlint on commit + CI). Types: `feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `ci`, `perf`. The scope is a workspace-derived `scope-enum` (module dirs, apps, plus `ci`/`deps`/`rules`/`repo`/`tooling`); an unlisted scope fails - check with `pnpm commitlint --from HEAD~1` or omit the scope. `feat(wallet): atomic debit command port` -- **Subject must start lowercase** (`subject-case`), acronyms included - write `feat(pam): kyc status filter`, not `KYC`. This applies to the **PR title too**: squash-merge derives the `dev` commit message from the PR title, which local commit hooks never lint - an uppercase title lands a failing commit on `dev`. -- **One PR = one concern.** Stage files explicitly; never `git add -A` with foreign changes in the tree. -- **Green before review:** `pnpm verify` passes; `pnpm regen` after any contract/schema change. -- **Branch off `dev`; never commit directly to `dev`/`stage`.** Promotion chain `dev -> stage` + release tags. Never push without an explicit per-action confirmation. -- **PR description carries intent:** what / why / acceptance criteria / ticket key. -- **No sensitive/internal data in titles, descriptions, or commits** - they are the public record. Bare ticket key (`ABC-45`), never the URL; no internal links, hostnames, secrets, PII. When in doubt, leave it out. - -## 13. Enforcement - -- `pnpm verify` = typecheck + unit tests + oxlint + module-shape + `pnpm check:boundaries` (dependency-cruiser). -- Two-layer boundaries (per-edit oxlint + whole-graph cruiser) - don't work around a violation; fix the import. -- Module structure + naming are lint-enforced (`oss-module-shape/*` oxlint JS plugin): files sit in a canonical layer dir, `service/` files end `.service.ts`, `__tests__/` files end `.test.ts`, filenames kebab-case, no inline `pgEnum` value arrays. -- oxlint config is split: the published `@openora/core/oxlint/oxlintrc.json` holds the universal, stack-agnostic rules (base rules, `typescript/no-explicit-any`, `typescript/no-non-null-assertion`, `typescript/consistent-type-definitions`, `import/no-cycle`, `import/no-duplicates`) - the single source of truth a consumer extends via `"extends": ["./node_modules/@openora/core/oxlint/oxlintrc.json"]`. The root `.oxlintrc.json` here `extends` that shared config and adds only OSS-internal rules (`oss-boundaries/*`, `oss-module-shape/*`, `unicorn/filename-case`) that need the local `jsPlugins`. -- Pre-commit runs `pnpm check:boundaries`; CI runs `pnpm verify` + the no-drift check. -- Agent rules mirror this standard - generated from `.rulesync/` via `pnpm gen:agents`. +## Never (lint-enforced unless noted) + +- `any` (tests included), `!` non-null assertions, `arr[i]!`, `as` casts to silence the compiler (`as const` is fine; test doubles go through the `mock` helper). +- `interface`, TS `enum`, decorators, inheritance for reuse, default exports (except `plugin.ts` + `drizzle.config.ts`). +- Hand-written duplicates of an inferrable type, re-inferring an imported schema, re-typing derived schema fields. +- Raw `z.uuid()` (use `UuidSchema`), inline `z.enum([...])` outside a contract dir, inline `pgEnum` value arrays. +- Inline `fetch`/`axios` in module code - third-party access is a port + adapter bound at the root. +- Comments. The only exception is a fact the code cannot contain (external-system behaviour, a spec constraint) and JSDoc on a public export. A rationale is not a fact - it goes in the commit or an ADR. +- Deep (`../../`+) relative imports that leave your zone/module, imports of another module's internals, import cycles, deep `dist/`/`src/` paths into another package. +- Hand-edited generated files: migrations, `docs/openapi.json`, `docs/catalog.json`, per-tool agent mirrors. + +## Always + +- **One source of truth per shape - infer, never hand-write:** `z.infer`, `typeof x.$inferSelect`. +- **Schema-first at every boundary** (HTTP, config, env, events); validate once at the edge, trust the type after. +- **Enum-like sets are a values + schema + type triple on the contract surface**; `pgEnum` derives from the tuple. +- **Entity ids typed through their owning type** (`roleId: AdminRole['id']`, never a bare `string`). +- **Guard clauses first, main path last; brace every control statement; >3 params -> one named object.** +- **Construct objects by spread + override**, never a hand-copied field list. +- **Side effects at the edges**; events emit after the DB commit; money paths are transactional AND idempotent (a DB guard inside the transaction, not just an idempotency key). +- **Typed, named error classes** from the shared factories, mapped to transport in the router's `mapErrors`. +- **Cross-module coupling only via** a domain event, a command port, a shared contract, or a read-only `/schema` subpath. +- **Reuse the shared helpers** (`findOneOrThrow`, `pageToOffset`, `assertOwnership`, `serializeRow`, `createEventStreamGenerator`, `IdInputSchema`/`PageQuerySchema`) instead of re-rolling them - full table in `docs/standards/module-structure.md`. +- **Tests co-locate in `__tests__/`**; a file using `createTestDb`/`createTestRedis` is named `*.int.test.ts` (integration tier, needs docker pg + redis). Test behaviour, not the query builder; always cover authz negatives. +- **Pin exact dependency versions** (no `^`/`~`), and add a dependency deliberately. +- **Green before review:** `pnpm verify` passes, `pnpm regen` after any contract/schema change. Conventional commits, lowercase subject, one PR per concern. Never push without explicit confirmation. diff --git a/.rulesync/rules/db-conventions.md b/.rulesync/rules/db-conventions.md index 3912a185..b2bfb68c 100644 --- a/.rulesync/rules/db-conventions.md +++ b/.rulesync/rules/db-conventions.md @@ -24,11 +24,11 @@ description: SQL / Drizzle conventions - snake_case identifiers, timestamptz, ke The authoritative SQL rule for the platform. Tables live in a module's `src/schema/index.ts`; every module owns its own `drizzle.config.ts` + co-located `drizzle/migrations/` history (ADR-0027). Layering -and DI live in `clean-architecture`; import boundaries in `overview` > Dependency rules. This is SQL only. +and DI live in `docs/standards/module-structure.md`; import boundaries in `overview` > Dependency rules. This is SQL only. ## Enums - pgEnum derives from the contract tuple -Never an inline value array (lint error `oss-module-shape/no-inline-pg-enum`). Declare the set once on the contract surface as a tuple + `z.enum` + inferred type (see `conventions` section 3), then `pgEnum('x_status', X_STATUSES)` - the DB enum can never drift from the contract, and consumers import the same values. Reference: `wallet/schema/index.ts`. +Never an inline value array (lint error `oss-module-shape/no-inline-pg-enum`). Declare the set once on the contract surface as a tuple + `z.enum` + inferred type (see `docs/standards/types.md`), then `pgEnum('x_status', X_STATUSES)` - the DB enum can never drift from the contract, and consumers import the same values. Reference: `wallet/schema/index.ts`. ## Identifiers - snake_case everywhere diff --git a/.rulesync/rules/overview.md b/.rulesync/rules/overview.md index d9a1bda7..37bc8f28 100644 --- a/.rulesync/rules/overview.md +++ b/.rulesync/rules/overview.md @@ -10,7 +10,7 @@ globs: Canonical brief for AI agents and humans. Per-tool files (`AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`, `.codex/config.toml`, subagent + command mirrors) are generated by [rulesync](https://github.com/dyoshikawa/rulesync) from `.rulesync/`. Edit the source, run `pnpm gen:agents`. Never hand-edit generated files. -Sibling rules (load on demand; don't reopen settled questions): `conventions` (code standard), `clean-architecture` (module layering, DI, ports, shared helpers), `messaging-and-microservices` (async seams, command vs event vs job, outbox), `db-conventions` (SQL / Drizzle). +Sibling rules (load on demand; don't reopen settled questions): `conventions` (the always-on code standard, with a table routing to the deep-dive file in `docs/standards/`), `messaging-and-microservices` (async seams, command vs event vs job, outbox), `db-conventions` (SQL / Drizzle). Module layering, DI, ports and the shared-helper table live in `docs/standards/module-structure.md`. ## Mission @@ -63,10 +63,10 @@ extensions.config.ts # the single registry of enabled plugins - **Admin-only route** -> `plugin.ts` resolves `AdminGuard` (`c.get(ADMIN_GUARD)`, seeded by `createApp`) and passes it into the router; `await adminGuard.assert(context)` is the handler's FIRST line. The single admin-enforcement point - never re-implement the role check. - **New DB table** -> a Drizzle `pgTable` in the module's `schema/index.ts`; run `propose-table-change` (MCP) first, then `pnpm regen`. See `db-conventions`. - **Reusable Zod schema** -> `packages/core/src/contracts/schemas/.ts`. Module-local schemas in the module's `contract/`. -- **Enum / status value set** -> a values + schema + type triple on the contract surface (cross-domain: core `contracts/schemas/`; domain-local: the module's `contract/`), pgEnum derived from the tuple. `conventions` section 3 + `db-conventions` > Enums. +- **Enum / status value set** -> a values + schema + type triple on the contract surface (cross-domain: core `contracts/schemas/`; domain-local: the module's `contract/`), pgEnum derived from the tuple. `docs/standards/types.md` + `db-conventions` > Enums. - **Cross-module event** -> declare the payload in `domainEventSchemas` (`packages/core/src/contracts/schemas/events.ts`), emit via `EventBus`, subscribe with `ctx.events.on(...)`. ADR-0010; detail in `messaging-and-microservices`. - **Frontend UI** -> NOT here (headless). Consumer builds it over HTTP via `@openora/core/react`. -- **New data hook** -> `packages/core/src/react/hooks/` (domain-specific: that domain's `react/` dir). Hand-write `useMemo`/`useCallback` for stability-contract returns (consumer's React Compiler skips `node_modules`) - `conventions` section 7. +- **New data hook** -> `packages/core/src/react/hooks/` (domain-specific: that domain's `react/` dir). Hand-write `useMemo`/`useCallback` for stability-contract returns (consumer's React Compiler skips `node_modules`) - `docs/standards/react-sdk.md`. - **Operator config** (feature flags, brands, RG defaults) -> `platform-config.yaml`/`.json` via `loadPlatformConfig()` + `PlatformConfigSchema`, bound as `PLATFORM_CONFIG`. ADR-0013. - **Third-party integration** (PSP, KYC, aggregator, chat) -> adapter interface + `createToken` in `packages/core/src/contracts/adapters/.ts`, impl in the owning module's `adapters//`, bound in `plugin.ts` via `ctx.provide(TOKEN, () => new Impl())`. Never inline `fetch`/SDK calls. - **Background task** -> the `JOB_QUEUE` seam: `enqueue(queue('name'), payload, { idempotencyKey, delayMs, attempts, backoff, orderingKey })`; a worker overlay registers the handler via `ctx.jobs.worker(...)`. At-least-once: handlers idempotent (DB guard for money). ADR-0014. @@ -106,7 +106,9 @@ pnpm setup # first time: docker + db + mcp + summary pnpm dev # turbo dev (docs, mcp) pnpm regen # tsconfig paths + openapi emit + drizzle generate + catalog pnpm db:seed # demo data (idempotent; admin@oss.dev / password123) -pnpm verify # the full gate: every check:* + test:unit + test:tools, in parallel +pnpm verify # the full gate: every check:* + test:unit + test:integration + test:tools, in parallel +pnpm test:unit # infra-free suite (~4s); *.int.test.ts run in test:integration (docker pg + redis) +pnpm db:setup:test:fresh # recreate the shared e2e db after editing an already-applied migration pnpm check:boundaries # just the whole-graph boundary + cycle gate pnpm check:drift # catalog/openapi staleness (CI-only; not part of verify) pnpm fix:lint # oxlint --fix; pair with fix:format diff --git a/.rulesync/skills/add-feature/SKILL.md b/.rulesync/skills/add-feature/SKILL.md index c7963979..99aadadf 100644 --- a/.rulesync/skills/add-feature/SKILL.md +++ b/.rulesync/skills/add-feature/SKILL.md @@ -35,7 +35,7 @@ Platform-core twin of the consumer `add-feature` skill. Use in this repo when a ### 1. Resolve input + collect context (read-only) -`$ARGUMENTS` is a work-order path (eg `~/.claude/plans/-oss.md`) or a ticket. Read it (goal, consumer feature it unblocks, core surface, contract/schema impact, acceptance), then gather in parallel: the ticket (tracker MCP if connected), `docs/` ADRs + `catalog.json` + `openapi.json`, the rule docs (`conventions`, `clean-architecture`, `messaging-and-microservices`, `db-conventions`), the codebase (`oss-dev` MCP + Explore), chat/wiki for prior design discussion (search the ticket key). Run the `enhance-prompt` pre-step on the ask first, so this gather stays scoped and 1a grills against a clear brief. +`$ARGUMENTS` is a work-order path (eg `~/.claude/plans/-oss.md`) or a ticket. Read it (goal, consumer feature it unblocks, core surface, contract/schema impact, acceptance), then gather in parallel: the ticket (tracker MCP if connected), `docs/` ADRs + `catalog.json` + `openapi.json`, the rule docs (`conventions`, `messaging-and-microservices`, `db-conventions`) plus the relevant `docs/standards/` files, the codebase (`oss-dev` MCP + Explore), chat/wiki for prior design discussion (search the ticket key). Run the `enhance-prompt` pre-step on the ask first, so this gather stays scoped and 1a grills against a clear brief. #### 1a. Grill the user before planning (MANDATORY) diff --git a/.rulesync/skills/create-pr/SKILL.md b/.rulesync/skills/create-pr/SKILL.md index 89b3d944..702732f5 100644 --- a/.rulesync/skills/create-pr/SKILL.md +++ b/.rulesync/skills/create-pr/SKILL.md @@ -21,7 +21,7 @@ Any other branch: target `dev`. 1. **Determine source + target.** `git branch --show-current` -> the table above. 2. **Scope the commit.** `git status -s`. Commit ONLY changes belonging to this unit of work. Foreign/pre-existing edits in the tree: stage your files explicitly, tell the user what you left out. Never `git add -A` blindly. -3. **Commit.** Conventional-commit message with a valid scope (see `conventions` section 12). No AI attribution trailers. Keep the message free of sensitive/internal data (below). +3. **Commit.** Conventional-commit message with a valid scope (see `docs/standards/git-delivery.md`). No AI attribution trailers. Keep the message free of sensitive/internal data (below). 4. **Verify before pushing:** `pnpm verify`. Don't push a red tree. 5. **Push - STOP first.** Report the commit SHA, then ask for an explicit per-action "yes push". Invoking this skill is NOT push authorization; shared branches (`dev`, `stage`) especially. Only after the yes: `git push -u origin `. 6. **Open the PR** (after the confirmed push). Read `.github/pull_request_template.md` and fill it - it is the source of truth for the structure, so follow whatever sections it currently defines and don't invent your own. Answer the HTML comment in each section, then delete the comment. Drop a section it tells you to drop; tick a box only if it actually holds. diff --git a/.rulesync/skills/enhance-prompt/SKILL.md b/.rulesync/skills/enhance-prompt/SKILL.md index 6e593f76..56628077 100644 --- a/.rulesync/skills/enhance-prompt/SKILL.md +++ b/.rulesync/skills/enhance-prompt/SKILL.md @@ -36,7 +36,7 @@ Match effort to the ask: a small task gets a one-line restatement, not a full br - **Objective** - the outcome, in priority order. - **Scope** - what's in; what's explicitly out. - **Context** - the load-bearing facts found, with links (issue, doc, `file:line`). -- **Constraints** - conventions, clean-architecture layering, minimal-dependency + token budget, the public / open-source surface. +- **Constraints** - conventions, module layering (`docs/standards/module-structure.md`), minimal-dependency + token budget, the public / open-source surface. - **Deliverables** - what "done" produces. - **Guardrails** - what not to touch; reversible-only; confirm-before-X. - **Open questions** - only the blocking ones. diff --git a/.rulesync/skills/oss-review/SKILL.md b/.rulesync/skills/oss-review/SKILL.md index 271c44cf..d7e8e3ed 100644 --- a/.rulesync/skills/oss-review/SKILL.md +++ b/.rulesync/skills/oss-review/SKILL.md @@ -46,7 +46,7 @@ function does, open it; if you can't cite it, fetch it. These docs are the singl cite them in findings: - `.claude/rules/conventions.md` - the portable code standard (philosophy, naming, types, functions, comments, testing, errors, deps, git). -- `.claude/rules/clean-architecture.md` - module layering, DI, ports & adapters, shared helpers, FK rule. +- `docs/standards/module-structure.md` - module layering, DI, ports & adapters, shared helpers, FK rule. - `.claude/rules/messaging-and-microservices.md` - command vs event vs job, outbox, service manifest. - `CLAUDE.md` (AGENTS.md) - architecture pillars, dependency rules, forbidden patterns, the "Definition of done" audit requirement. - The touched module's own `AGENTS.md` and any relevant `docs/adr/*.md`. diff --git a/.rulesync/subagents/contract-reviewer.md b/.rulesync/subagents/contract-reviewer.md index d8d3da0c..0d197e30 100644 --- a/.rulesync/subagents/contract-reviewer.md +++ b/.rulesync/subagents/contract-reviewer.md @@ -15,7 +15,7 @@ Stance: assume the change is BROKEN until you trace it working - review to falsi ## Grounding -If the orchestrator passed a base ref + changed-file list, use them - do not re-scope the diff. Otherwise: `git diff origin/dev...HEAD --name-only`. READ each changed file before judging it - never infer behavior from a hunk. Compare route changes against the committed `docs/openapi.json`. Cite the rule doc (`conventions`, `clean-architecture`, `db-conventions`, root `AGENTS.md`) or ADR each finding rests on. +If the orchestrator passed a base ref + changed-file list, use them - do not re-scope the diff. Otherwise: `git diff origin/dev...HEAD --name-only`. READ each changed file before judging it - never infer behavior from a hunk. Compare route changes against the committed `docs/openapi.json`. Cite the rule doc (`conventions`, `docs/standards/*.md`, `db-conventions`, root `AGENTS.md`) or ADR each finding rests on. ## Checklist diff --git a/.rulesync/subagents/dev.md b/.rulesync/subagents/dev.md index c1b995b3..8b027525 100644 --- a/.rulesync/subagents/dev.md +++ b/.rulesync/subagents/dev.md @@ -24,7 +24,7 @@ Your prompt contains requirements + acceptance criteria. Build to those. If the ## Before writing code -1. Read root `AGENTS.md` (decision tree, boundaries, forbidden patterns) and the sibling rules (`conventions`, `clean-architecture`, `messaging-and-microservices`, `db-conventions`). Follow exactly. +1. Read root `AGENTS.md` (decision tree, boundaries, forbidden patterns) and the sibling rules (`conventions`, `messaging-and-microservices`, `db-conventions`) plus the `docs/standards/` file matching what you are changing. Follow exactly. 2. Read the touched module's `AGENTS.md` and any related `docs/adr/`. 3. Inspect current state via `oss-dev` MCP: `list-modules`, `describe-module`, `list-routes` (collision check), `query-openapi`, `get-drizzle-schema`, `propose-table-change` (before any table), `schema-get`. 4. Pick the home via the decision tree. Use the scaffolders (`pnpm gen module|route|plugin|adapter|job-worker`) - don't hand-write skeletons. diff --git a/.rulesync/subagents/module-author.md b/.rulesync/subagents/module-author.md index 9891f470..470c0d56 100644 --- a/.rulesync/subagents/module-author.md +++ b/.rulesync/subagents/module-author.md @@ -22,7 +22,7 @@ You are an expert TypeScript / Hono / oRPC engineer implementing a module for th ## Grounding (do this first) -1. Read root `AGENTS.md` + sibling rules (`conventions`, `clean-architecture`, `db-conventions`). Follow exactly. +1. Read root `AGENTS.md` + sibling rules (`conventions`, `db-conventions`) + `docs/standards/module-structure.md`. Follow exactly. 2. Read an existing module (eg `packages/core/src/wallet/`) for the exact file shape. 3. Check current state via `oss-dev` MCP: `list-modules`, `describe-module`, `list-routes`, `query-openapi` (route collisions), `get-drizzle-schema`, `propose-table-change` (before ANY table). 4. Unanswered domain question in the brief? STOP and spawn `expert` before writing code. diff --git a/.rulesync/subagents/quality-reviewer.md b/.rulesync/subagents/quality-reviewer.md index ae623420..4120b502 100644 --- a/.rulesync/subagents/quality-reviewer.md +++ b/.rulesync/subagents/quality-reviewer.md @@ -16,7 +16,7 @@ Stance: assume the change is BROKEN until you trace it working - review to falsi ## Grounding - Read `.claude/rules/conventions.md` IN FULL and enforce all of it - every section, not a subset. The lens checklists below are high-signal reminders, not the boundary of the review. -- When the diff touches module layering, DI, or ports, also apply `.claude/rules/clean-architecture.md`; for SQL/Drizzle, `.claude/rules/db-conventions.md`; and the touched module's `AGENTS.md`. +- When the diff touches module layering, DI, or ports, also apply `docs/standards/module-structure.md`; for SQL/Drizzle, `.claude/rules/db-conventions.md`; and the touched module's `AGENTS.md`. - Where no repo rule covers a problem, judge by established industry practice (algorithmic complexity, DB query patterns and indexing, transaction scope, React render behavior, error-handling hygiene, API design) and name the principle in the finding instead of a rule doc. - Verify before you claim: for library/framework API behavior, check current docs (context7 MCP or web search) instead of assuming from memory; use the `oss-dev` MCP tools for routes/schemas. If the orchestrator passed a ticket key and an issue-tracker tool is available, you may fetch it for acceptance criteria - never quote raw ticket text in findings. @@ -38,7 +38,7 @@ The orchestrator passes you the base ref and changed-file list - do not re-scope - [ ] Bounded fan-out - concurrent DB work over a query result goes through `mapConcurrent(items, limit, fn)`, never an uncapped `Promise.all(rows.map(...))` (one pool connection per row starves the instance at scale). Watch for fan-out hidden behind a called method, and for oversized `IN (...)` lists. - [ ] No unbounded reads - lists paginate; no `SELECT *` of a hot table into memory to filter in JS. - [ ] Hot-path work not repeated per call when it can be computed once (schema parsing, regex compilation, config reads). -- [ ] React hooks: stability-contract returns use `useMemo`/`useCallback` (conventions section 7). +- [ ] React hooks: stability-contract returns use `useMemo`/`useCallback` (`docs/standards/react-sdk.md`). ### Duplication diff --git a/docs/standards/comments.md b/docs/standards/comments.md new file mode 100644 index 00000000..a5677660 --- /dev/null +++ b/docs/standards/comments.md @@ -0,0 +1,20 @@ +# Comments and documentation + +Detail for the "zero comments" line in `conventions`. Read this before writing any comment or JSDoc block. + +- **Zero comments. A comment is an exception you must justify, not a nicety.** Assume the answer is "no comment" and let the code carry the meaning. +- **The only thing that earns one: a fact the code CANNOT contain** - an external system's behaviour, a third-party bug, a spec/regulatory constraint. The test is whether a careful reader would otherwise "fix" the code and break it. `// Stripe rounds half-to-even; mirror it so our totals reconcile.` +- **A reason is not a fact - it does not earn a comment.** Why this order, why 2 retries and not 4, why not the obvious approach, what a block does, what changed: all of that goes in the commit message, the PR description, or an ADR. Those are versioned and reviewed; an inline rationale is neither, and it rots in place. Naming the thing well (`PLAYER_FACING_TIMEOUT_MS`) beats a paragraph above it. +- **If a block needs a comment to be understood, rename or extract first** - a comment is the fallback after that fails, never the first move. Writing one is the signal that the naming or the decomposition is wrong. +- **Never in tests.** A test name states the behaviour and the assertions state the evidence. Seeded values, fixture choices and timing tricks get named constants or helpers, not narration. +- **Same bar in config, CI and infra files** (`turbo.json`, workflow YAML, compose). Step names and keys are self-describing; step ordering and tuning rationale belong in the commit that introduced them. +- **Never** restate a name (`// increment the counter`), narrate steps (`// step 2`), announce edits (`// added for X`), or divide sections (`// ---`, `// ===`). +- **JSDoc on every exported function/class >~15 lines or with non-obvious params.** Multiline `/** ... */` block (opening and closing on their own lines). Document the surprising contract, not the name. +- **`// TODO:` for deferred work, `// FIXME:` for known-broken code** - greppable, with context and an issue key where one exists. Never bare. + + ```ts + // TODO: replace polling with the webhook once BE ships it (ABC-312) + // FIXME: race - two admins approving the same withdrawal double-credit the player + ``` + +- **`// mock:` marks placeholder data / stubbed behavior** so throwaway code stays findable. `// mock: fixed rate until the FX adapter lands` diff --git a/docs/standards/enforcement.md b/docs/standards/enforcement.md new file mode 100644 index 00000000..d1bfcc37 --- /dev/null +++ b/docs/standards/enforcement.md @@ -0,0 +1,11 @@ +# Enforcement + +Detail for the enforcement line in `conventions`. Read this when a gate fails or when adding a lint rule. + +- `pnpm verify` = typecheck + lint + format + boundaries + module-shape + deprecations + `test:unit` + `test:integration` + `test:tools`. CI adds `pnpm check:drift`. +- Two-layer boundaries - per-edit oxlint (`oss-boundaries/*`) plus the whole-graph dependency-cruiser (`pnpm check:boundaries`, catches transitive edges, barrel laundering, dynamic `import()`). Don't work around a violation; fix the import. +- Module structure + naming are lint-enforced (`oss-module-shape/*` oxlint JS plugin, `tools/lint/oxlint-module-shape-plugin.mjs`): files sit in a canonical layer dir, `service/` files end `.service.ts`, `__tests__/` files end `.test.ts`, an infra-backed test ends `.int.test.ts`, filenames kebab-case, no inline `pgEnum` value arrays. +- oxlint config is split: the published `@openora/core/oxlint/oxlintrc.json` holds the universal, stack-agnostic rules (base rules, `typescript/no-explicit-any`, `typescript/no-non-null-assertion`, `typescript/consistent-type-definitions`, `import/no-cycle`, `import/no-duplicates`) - the single source of truth a consumer extends via `"extends": ["./node_modules/@openora/core/oxlint/oxlintrc.json"]`. The root `.oxlintrc.json` here `extends` that shared config and adds only OSS-internal rules (`oss-boundaries/*`, `oss-module-shape/*`, `unicorn/filename-case`) that need the local `jsPlugins`. +- Generated migrations are byte-sensitive: drizzle hashes each file to decide what is already applied, so formatting hooks skip `**/drizzle/migrations/*.sql` and nothing may hand-edit them. +- Pre-commit runs `pnpm check:boundaries` + `pnpm check:types`; CI runs `pnpm verify` + the no-drift check. +- Agent rules mirror this standard - generated from `.rulesync/` via `pnpm gen:agents`. diff --git a/docs/standards/errors.md b/docs/standards/errors.md new file mode 100644 index 00000000..a2019625 --- /dev/null +++ b/docs/standards/errors.md @@ -0,0 +1,46 @@ +# Error handling + +Detail for the "typed errors at the edge" line in `conventions`. Read this when adding an error class, a catch, or a money path. + +- **Fail fast at boundaries with typed errors** - `Schema.parse(raw)` throws early. +- **No silent catches** - log with context and rethrow, or handle explicitly. + + ```ts + // bad - swallows the cause, the caller sees a lie + try { + await psp.capture(id); + } catch { + return { ok: false }; + } + // good - context in, error out + try { + await psp.capture(id); + } catch (err) { + this.logger.error({ err, withdrawalId: id }, 'psp capture failed'); + throw err; + } + ``` + +- **Typed, named error classes mapped to transport at the edge.** Use the shared factories (`makeNotFoundError`/`makeOwnershipError`/`makeConflictError`); the router's `mapErrors` keys off the exported class. `export const WalletNotFoundError = makeNotFoundError('Wallet');` +- **Error factories keep the SAME exported const identifier** (`export const WalletNotFoundError = makeNotFoundError('Wallet')`) - routers import the class and `mapErrors` keys off it. + + ```ts + // bad - the router hand-rolls transport mapping and leaks a raw message + catch (err) { + throw new ORPCError('CONFLICT', { message: (err as Error).message }); + } + // good - the service throws its typed class, the router maps it + mapErrors({ CONFLICT: IdempotencyKeyReuseError }, () => wallet.deposit(input)); + ``` + + Canonical router to copy: `packages/core/src/wallet/router/index.ts`. + +- **`ORPCError.message` is an English fallback for logs/tooling, not player-facing copy** - never render it directly in a UI. Consumers key UI copy off `.code` (the error category) plus typed `.data` fields (e.g. a `reason` discriminator); add a new field to `data`, not to `message`, when a UI needs to tell cases apart. +- **Money / critical paths are transactional and idempotent** - a DB guard inside the transaction, not just an `idempotencyKey` (delivery is at-least-once): + + ```ts + await db.transaction(async (t) => { + if (await ledgerExists(t, idempotencyKey)) return; + await insertLedger(t, { idempotencyKey, amount }); + }); + ``` diff --git a/docs/standards/functions.md b/docs/standards/functions.md new file mode 100644 index 00000000..6831e607 --- /dev/null +++ b/docs/standards/functions.md @@ -0,0 +1,87 @@ +# Functions, classes, and side effects + +Detail for the "functional and declarative" line in `conventions`. Read this when writing or refactoring a function, a service method, or a constructor. + +- **Pure functions with dependencies passed in; side effects at the edges.** `price(cart, rate)` - the caller owns the IO, not `price(cart)` reading a rate inside. +- **Immutability - derive, don't mutate.** `{ ...user, roles: [...user.roles, 'admin'] }`, not `user.roles.push('admin')`. +- **Construct objects by spread + override, never hand-copy field-by-field.** When a new object mostly mirrors an existing one (a DB insert from a validated input, a patch, a re-shaped payload, a row -> DTO), spread the source and set only what differs - `db.insert(x).values({ ...input, id, createdAt, hash })` - never re-list `field: input.field` per key. The hand-written copy is pure noise that silently drifts the moment a field is added on one side. For a row -> DTO that only turns `Date`/`Decimal` into strings, use `serializeRow(row, { dateFields, decimalFields })`, not a manual map. Three cases keep fields explicit, and only these: (1) **order-sensitive serialization** - a hash/signature canonical form ties its bytes to key order, so list keys by hand and treat the list as append-only; (2) the source carries **fields the target must not receive** - spread then `.omit`/destructure them off, or list explicitly; (3) **null vs undefined matters at a boundary that won't coerce** - normalize once (`actorId: input.actorId ?? null`) rather than relying on a spread passing `undefined` through. + + ```ts + // bad - re-lists every field; adding `userAgent` to the schema silently drops it here + .values({ id, actorId: input.actorId ?? null, actorType: input.actorType, action: input.action }) + // good - spread the validated input, override only the server-computed fields + .values({ ...input, createdAt: input.createdAt.toISOString() }) + ``` + +- **A `class` is only a thin dependency-holding shell at a composition root; methods delegate to pure functions.** +- **No inheritance for reuse - compose.** No decorators anywhere. +- **Short, single-purpose functions.** If you'd comment "// step 2" inside a function, extract it. +- **Guard clauses first, main path last.** Early-return the edge/simple cases up top; keep the biggest branch as the final unguarded return - flatter and easier to read than wrapping it in an `if`. + + ```ts + // bad - main branch nested in an if + if (Array.isArray(v)) { + return v.map(f); + } + if (v !== null && typeof v === 'object') { + return heavy(v); + } + return v; + // good - edge cases guarded, heavy branch falls through to the end + if (Array.isArray(v)) { + return v.map(f); + } + if (v === null || typeof v !== 'object') { + return v; + } + return heavy(v); + ``` + +- **Always brace control statements** - every `if`/`else`/`for`/`while` body in `{ }`, even one-liners (lint: `curly`). + + ```ts + // bad + if (isActive) return true; + // good + if (isActive) { + return true; + } + ``` + +- **Don't annotate a return type TypeScript can infer** - the body is the single source of truth. Annotate ONLY when: + - inference can't (recursion), or + - you deliberately widen/narrow, or + - it's a **published SDK export with no re-checking seam** - `/react` hooks + the typed client + plain-value `/server` helpers - where the explicit type IS the public contract (an inferred return silently leaks a refactor as a downstream breaking change). + + Routers, service methods, contracts, schemas, and plugins stay inferred: a seam re-checks them (oRPC validates handler output against the contract) or the type is an unspellable oRPC/Drizzle structure. Argument types always explicit. + +- **More than 3 parameters -> a single named-object param.** Four or more positionals are unreadable at the call site (`f(txn, ns, id, key, amt, cur, vals)` - which string is which?) and silently break when two share a type. Pass one object and destructure; a leading handle like a `tx`/`trx` may stay positional. Applies to functions, service methods, and constructors alike. + + ```ts + // bad - 7 positionals, three interchangeable strings + insertIdempotentTransaction( + txn, + namespace, + walletId, + rawIdempotencyKey, + amount, + currency, + values, + ); + // good - named object, self-documenting call site + insertIdempotentTransaction(txn, { + namespace, + walletId, + rawIdempotencyKey, + amount, + currency, + values, + }); + ``` + +- **Named exports only - no default exports.** Two sanctioned exceptions: `plugin.ts` (the loader reads `mod.default`, `load-plugins.ts:85`) and `drizzle.config.ts` (drizzle-kit requires it). + +## State and side effects + +- **Side effects at the edges** (services, adapters, plugins, handlers), never in pure helpers. Events emit from the service after the DB commit. +- **Server state is owned by the data layer** - no ad-hoc shadow caches. diff --git a/docs/standards/git-delivery.md b/docs/standards/git-delivery.md new file mode 100644 index 00000000..e199b7f7 --- /dev/null +++ b/docs/standards/git-delivery.md @@ -0,0 +1,11 @@ +# Git and delivery + +Detail for the delivery lines in `conventions`. Read this before committing, opening a PR, or writing a commit message. + +- **Conventional commits, enforced** (commitlint on commit + CI). Types: `feat`, `fix`, `refactor`, `chore`, `docs`, `test`, `ci`, `perf`. The scope is a workspace-derived `scope-enum` (module dirs, apps, plus `ci`/`deps`/`rules`/`repo`/`tooling`); an unlisted scope fails - check with `pnpm commitlint --from HEAD~1` or omit the scope. `feat(wallet): atomic debit command port` +- **Subject must start lowercase** (`subject-case`), acronyms included - write `feat(pam): kyc status filter`, not `KYC`. This applies to the **PR title too**: squash-merge derives the `dev` commit message from the PR title, which local commit hooks never lint - an uppercase title lands a failing commit on `dev`. +- **One PR = one concern.** Stage files explicitly; never `git add -A` with foreign changes in the tree. +- **Green before review:** `pnpm verify` passes; `pnpm regen` after any contract/schema change. +- **Branch off `dev`; never commit directly to `dev`/`stage`.** Promotion chain `dev -> stage` + release tags. Never push without an explicit per-action confirmation. +- **PR description carries intent:** what / why / acceptance criteria / ticket key. +- **No sensitive/internal data in titles, descriptions, or commits** - they are the public record. Bare ticket key (`ABC-45`), never the URL; no internal links, hostnames, secrets, PII. When in doubt, leave it out. diff --git a/.rulesync/rules/clean-architecture.md b/docs/standards/module-structure.md similarity index 78% rename from .rulesync/rules/clean-architecture.md rename to docs/standards/module-structure.md index fb369d04..bbac74d0 100644 --- a/.rulesync/rules/clean-architecture.md +++ b/docs/standards/module-structure.md @@ -1,16 +1,6 @@ ---- -root: false -targets: - - '*' -description: Clean-architecture conventions - module layering, DI, ports-and-adapters, and the shared helpers to reuse instead of re-rolling. -globs: - - 'packages/**' - - 'extensions/**' ---- +# Module structure, DI, and ports -# Clean architecture - -Settled conventions - don't reopen. Style: `conventions`. Here: structure + the syntax that prevents recurring mistakes. +Detail for the boundary lines in `conventions`. Read this when creating a module, wiring DI, or adding an integration. Settled conventions - don't reopen. ## Module layering (`packages/core/src///`) @@ -32,6 +22,26 @@ Service methods are data-in/data-out; side effects (DB writes, event emits, adap - Services take deps by type via constructor; never touch the container. `plugin.ts` builds them: `ctx.routers.add('wallet', (c) => createWalletRouter(new WalletService(c.get(DRIZZLE), c.get(EVENT_BUS), c.get(PAYMENT_ADAPTER))))`. - A dep captured in a closure is a smell - make it a port + token (canonical fix: `SEND_EMAIL` in identity). +```ts +// bad - the service reaches into the container and hides what it depends on +export class WalletService { + constructor(private readonly container: Container) {} + async deposit() { + const psp = this.container.get(PAYMENT_ADAPTER); + } +} +// good - deps are constructor params of their port type; plugin.ts does the resolving +export class WalletService { + constructor( + private readonly db: DrizzleService, + private readonly events: EventBus, + private readonly payments: PaymentAdapter, + ) {} +} +``` + +Canonical wiring to copy: `packages/core/src/wallet/plugin.ts` (provides `PAYMENT_ADAPTER`, `WALLET_COMMANDS`, `ADMIN_WALLET_REPORTING`, then builds the router). + ## Ports & adapters (hexagonal) Ports = interfaces + tokens in `packages/core/src/contracts/adapters/` (`PAYMENT_ADAPTER`, `KYC_ADAPTER`, `MESSAGE_BROKER`, `JOB_QUEUE`, `REALTIME_TRANSPORT`, `SEND_EMAIL`, ...). Adapters = impls in modules, bound in `plugin.ts`, swapped by a later-loading overlay re-`provide`ing the token. Services depend only on the port. A third-party integration is always a port + impl, never an inline `fetch`/SDK call. @@ -40,6 +50,16 @@ Ports = interfaces + tokens in `packages/core/src/contracts/adapters/` (`PAYMENT Sanctioned paths only: domain **events** (`EventBus`), **command ports** (a token the owner binds, eg `WALLET_COMMANDS`), shared **contracts**, read-only table reads via the owner's `/schema` subpath. Never import another module's internals (`no-cross-domain` = error; `pnpm check:boundaries` is the whole-graph gate). ADR-0015. +```ts +// bad - reaches into another module's service; check:boundaries fails +import { TagService } from '../../pam/tag/service/tag.service.js'; +// good - the owner binds a command port, the caller resolves the token +const tags = ctx.container.get(TAG_EVALUATION_COMMANDS); +await tags.evaluate(tx, { playerId }); +``` + +Canonical seams to copy: `wallet -> tag` via `TAG_EVALUATION_COMMANDS` (optional port, resolved lazily with `c.has(...)` in the router factory), `compliance -> pam` via the `kyc.status.changed` domain event. + Money + any needed-now mutation stay synchronous/transactional, never over events - use a command port: caller passes its own `tx` (`WALLET_COMMANDS.debit(tx, ...)`), atomic in-process yet splittable later; declare `dependsOn: ['']`. Cross-module schema reads are sanctioned but each one is an extraction blocker. ADR-0017. **Never write a deep (`../../`+) relative import that leaves your own top-level dir under `packages/core/src/` - reach every other zone through the package's own `@openora/core/*` subpath.** A relative import is only for staying inside your own module/zone (`./schema/index.js`, `../db/index.js`). The moment a `..` would cross into another domain, slice, or engine zone, it's a `@openora/core/*` import instead: @@ -68,8 +88,21 @@ Lint-enforced by `oss-module-shape/no-relative-zone-escape` (folded domains AND | push subscription -> SSE async generator | `createEventStreamGenerator((push) => svc.subscribe(push), { signal, prime })` | `@openora/core/server` | | canonical id/userId/pagination input | `IdInputSchema` / `UserIdInputSchema` / `PageQuerySchema` | `@openora/core/contracts` | -Error factories keep the SAME exported const identifier (`export const WalletNotFoundError = makeNotFoundError('Wallet')`) - routers import the class and `mapErrors` keys off it. - -## Testing - -Co-locate as `__tests__/.test.ts` (Vitest). A service test runs against real Postgres - `createTestDb([migrate])` from `@openora/core/testing` per file, plus `createTestRedis()` where the service caches or rate-limits. Only external vendors and cross-module ports are doubled, through the `mock` helper (`packages/core/src/testing/mock.ts`). Keep new logic covered; details in `conventions` section 10. +```ts +// bad - re-rolls first-row-or-throw and the DTO mapping by hand +const rows = await db.select().from(wallet).where(eq(wallet.id, id)); +if (rows.length === 0) { + throw new WalletNotFoundError(id); +} +return { + ...rows[0]!, + balance: String(rows[0]!.balance), + createdAt: rows[0]!.createdAt.toISOString(), +}; +// good +const row = findOneOrThrow( + await db.select().from(wallet).where(eq(wallet.id, id)), + new WalletNotFoundError(id), +); +return serializeRow(row, { dateFields: ['createdAt'], decimalFields: ['balance'] }); +``` diff --git a/docs/standards/react-sdk.md b/docs/standards/react-sdk.md new file mode 100644 index 00000000..22e2b5a3 --- /dev/null +++ b/docs/standards/react-sdk.md @@ -0,0 +1,32 @@ +# React SDK (`@openora/core/react` + domain `react/` dirs) + +Detail for the SDK line in `conventions`. Read this when adding or changing a hook, the typed client, auth, or realtime. + +Headless repo - only the SDK consumption layer lives here, no UI. + +- **One `useX` per concern, returning a plain object** (`{ wallet, isLoading }`). +- **Hand-write `useMemo`/`useCallback` wherever a returned value/function is part of a hook's stability contract** - the OPPOSITE of the consumer-app rule, because the consumer's React Compiler does not reprocess pre-built `node_modules`. Keep hooks Rules-of-React compliant so the consumer's compiler can optimize callers. +- **Server state is not client state** - key/cache/invalidate via the query lib, never a raw `useEffect(fetch)`. +- **A published SDK export annotates its return type** - it is the public contract; an inferred return silently leaks a refactor as a downstream breaking change (see `functions.md`). +- `react` never imports `server` or a module (lint: `no-react-to-runtime`). + +```tsx +// bad - fetch in an effect, a new object every render, inferred public return type +export function useWallet(userId: string) { + const [wallet, setWallet] = useState(); + useEffect(() => { + fetch(`/api/wallet/${userId}`) + .then((r) => r.json()) + .then(setWallet); + }, [userId]); + return { wallet, refetch: () => {} }; +} +// good - query lib owns server state, stability contract hand-memoized, return type explicit +export function useWallet(userId: string): UseWalletResult { + const { data, isLoading, refetch } = useQuery(walletQuery(userId)); + const reload = useCallback(() => void refetch(), [refetch]); + return useMemo(() => ({ wallet: data, isLoading, reload }), [data, isLoading, reload]); +} +``` + +Canonical hooks to copy: `packages/core/src/react/hooks/use-paginated-list.ts` (query + stable return), `use-event-stream.ts` (realtime SSE subscription). diff --git a/docs/standards/testing.md b/docs/standards/testing.md new file mode 100644 index 00000000..d376f383 --- /dev/null +++ b/docs/standards/testing.md @@ -0,0 +1,39 @@ +# Testing + +Detail for the testing lines in `conventions`. Read this before adding or restructuring a test. + +## Tiers + +- **Co-locate as `__tests__/.test.ts` (Vitest).** +- **A file that calls `createTestDb`/`createTestRedis` is named `.int.test.ts`** and runs in `test:integration` (needs docker Postgres + Redis); `test:unit` is the infra-free suite and stays a ~4s loop. Lint-enforced by `oss-module-shape/int-test-file-naming`. +- The end-to-end tier lives in `@openora/testing` (`bootTestApp` against a shared test db). Recreate that db with `pnpm db:setup:test:fresh` if a migration was edited after it was applied locally - drizzle hashes each migration file's bytes, so a stale hash re-runs an applied migration. + +## What is real, what is doubled + +- **Anything that touches the database is tested against real Postgres** - `createTestDb([migrate])` (`@openora/core/testing`) gives the file its own ephemeral database; `createTestRedis()` gives it a per-worker Redis logical DB. Never fake a query builder: a mocked chain proves a call order, not a result, so it misses the regressions that matter (unique-index dedupe, `FOR UPDATE` under concurrency, conditional atomic updates, cache invalidation). +- **What stays mocked:** external vendors (PSP, KYC, email, SMS, better-auth) and cross-module ports (`WALLET_COMMANDS`, `IdentityReader`, `EventBus`, `Logger`) - via the shared doubles in `packages/core/src/testing/mock.ts` (`mock`, `makeEventBus`, `makeAuditWriter`, `makeAdminGuard`, `testContext`), never a hand-rolled one in the test file. Engine (`server/**`) tests cannot import a domain schema (ADR-0024/0025), so they use the in-process implementations re-exported from `@openora/core/testing`. +- Every tier binds the drivers production binds (ADR-0032) - there are no in-process broker/queue/cache/rate-limiter doubles to fall back to. + +## How to write them + +- **Test behaviour, not implementation** - assert the resulting rows, cache state, and return value, not that a builder method was called: `expect(await service.get(id)).toEqual(user)`, not `expect(db.select).toHaveBeenCalled()`. + + ```ts + // bad - a mocked builder chain; proves a call order, not a result + const db = { select: vi.fn().mockReturnValue({ from: vi.fn().mockResolvedValue([row]) }) }; + expect(db.select).toHaveBeenCalled(); + // good - real Postgres per file, external ports doubled through the mock helper + let db: TestDb; + beforeAll(async () => { + db = await createTestDb([migrateProfile, migrate]); + }); + afterAll(() => db.drop()); + const svc = new TagService(db.drizzle, makeEventBus()); + await expect(svc.assign(assignment)).rejects.toThrow(TagNotFoundError); + ``` + + Canonical integration test to copy: `packages/core/src/pam/tag/__tests__/tag.service.int.test.ts` (ephemeral db, seeded rows, event assertions). Pure-logic reference: `packages/core/src/wallet/__tests__/rail-for.test.ts`. + +- **Cover new logic as part of the change** - unit for pure fns; always include authz negatives. +- **Deterministic and isolated:** no shared mutable state, no real network, seedable data. Real-infra suites stay parallel-safe - own your database and Redis keys, never assume an empty shared one. +- Run one file or dir with `pnpm -F @openora/core vitest run `; find the tests touching a file with `pnpm -F @openora/core vitest related `. diff --git a/docs/standards/types.md b/docs/standards/types.md new file mode 100644 index 00000000..f521f733 --- /dev/null +++ b/docs/standards/types.md @@ -0,0 +1,42 @@ +# Types and data modeling + +Detail for the "infer, never hand-write" line in `conventions`. Read this when adding a schema, a type, or an enum-like value set. + +- **One source of truth per shape - infer, never hand-write.** `z.infer`, `typeof users.$inferSelect`, `Omit`. +- **Schema-first at every boundary** (HTTP, config, env, messages, events). Validate once at the edge, trust the type after. oRPC + Zod does this for routes; do the same for config/env/event payloads. +- **No `any`, anywhere - tests included.** `unknown` + narrowing, or the real type. Never hand-roll a duplicate of a type that's one import away (`$inferSelect`/`$inferInsert`, a shared `@openora/core/*` type) - it silently drifts from the source of truth. +- **Never re-infer an imported schema** - the type is exported once from the owning contract; consumers import it, never re-run `z.infer` (lint: `oss-module-shape/no-reinfer-imported-schema`). + + ```ts + export type PlayerProfile = z.infer; // bad + import type { Player } from '@openora/core/contracts'; // good + ``` + +- **No type casts (`as`) to silence the compiler - fix the root cause.** (`as const` is fine.) `as unknown as X` turns type-checking off entirely. If a symbol needs a type, give it one at the source (`createToken()`), don't cast at use sites. Exactly two sanctioned exceptions: + 1. **Test doubles** - route through the `mock` helper (`packages/core/src/testing/mock.ts`) so the cast lives in one audited place, never inline in a test. + 2. **Third-party type-inference boundaries** a library gives you no honest way to satisfy - one cast with a one-line `// Library boundary:` note. + + ```ts + // bad + const user = data as User; + const directory = { lookupPlayers } as unknown as AdminUserDirectory; + // good + const user = UserSchema.parse(data); + const TOKEN = createToken('CONFIG'); + const directory = mock({ lookupPlayers }); // cast confined to the helper + ``` + +- **Never `!` non-null assertions** - narrow or restructure instead (lint: `typescript/no-non-null-assertion`). + + ```ts + return toDto(row!); // bad + return toDto(findOneOrThrow(rows, new XNotFoundError(id))); // good + ``` + +- **Under `noUncheckedIndexedAccess`: `.at()`, destructuring-with-defaults, or an explicit guard - never `arr[i]!`.** `const [first = ''] = parts;` +- **`type` over `interface`** (lint-enforced). +- **Type entity ids through their owning type**: `User['id']`, never a bare `string` - the id's representation lives in one place and every signature follows when it changes (lint: `oss-module-shape/no-bare-string-id-param`). Bad: `roleId: string`. Good: `roleId: AdminRole['id']`. +- **Derive related schemas, never re-type fields**: `UserSchema.partial().omit({ id: true })`. +- **Enum-like value sets are a values + schema + type triple, declared once on the contract surface**: `X_STATUSES = [...] as const` -> `XStatusSchema = z.enum(X_STATUSES)` -> inferred `XStatus`. Anything in a route contract is public API - consumers need the runtime values (dropdowns, badge maps), so types alone are not enough. Never TS `enum`; never a second hand-typed copy of the set (in a consumer either - import it). Inline `z.enum([...])` outside a contract dir is a lint error (`oss-module-shape/no-inline-z-enum-outside-contract`). +- **Reuse `UuidSchema`** (`@openora/core/contracts`) for uuid fields, never a raw `z.uuid()`/`z.string().uuid()` - one source of truth for the shape (lint: `oss-module-shape/no-raw-z-uuid`). +- **Make illegal states unrepresentable** - discriminated unions (`{ status: 'loading' } | { status: 'ok'; data } | { status: 'error'; error }`) over optional-flag soup (`{ loading: boolean; data?: T; error?: Error }`). diff --git a/packages/testing/src/__tests__/fixtures/test-kyc-config-plugin.ts b/packages/testing/src/__tests__/fixtures/test-kyc-config-plugin.ts index 12b4bd55..8788ce3a 100644 --- a/packages/testing/src/__tests__/fixtures/test-kyc-config-plugin.ts +++ b/packages/testing/src/__tests__/fixtures/test-kyc-config-plugin.ts @@ -41,7 +41,7 @@ class ControllablePendingKycAdapter implements KycAdapter { /** * Test-fixture overlay - binds `PLATFORM_CONFIG` (kyc gate + re-KYC thresholds) and swaps * `KYC_ADAPTER` for a controllable stub. Append last in a test's `plugins` array so both - * bindings win over the defaults (last-registration-wins; see clean-architecture > ports). + * bindings win over the defaults (last-registration-wins; see docs/standards/module-structure.md > ports). */ export default definePlugin({ id: 'test-kyc-config', diff --git a/tools/lint/oxlint-boundaries-plugin.mjs b/tools/lint/oxlint-boundaries-plugin.mjs index cb8d7d05..17b6bb98 100644 --- a/tools/lint/oxlint-boundaries-plugin.mjs +++ b/tools/lint/oxlint-boundaries-plugin.mjs @@ -162,7 +162,7 @@ const noAdhocZodInRouter = { `Ad-hoc Zod (z.${callee.property.name}(...)) in a router. Define the shape in ` + "the module's contract slice (its /contract dir, exported as @openora//contracts) - " + 'the source of truth for validation + OpenAPI + the typed client - and reference it. ' + - 'See clean-architecture.md.', + 'See docs/standards/module-structure.md.', }); } }, From ac23878e3212ccf5290ec0cc912b2c236b786eae Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 15:53:32 +0200 Subject: [PATCH 05/17] ci: serialise the two integration suites Core's suite flushes Redis logical DBs 0-15 (VITEST_POOL_ID % 16) while a bootTestApp e2e claims its own downward from 15, so running both concurrently lets one wipe the other's cache, streams and job queue mid-test. Verify runs them back to back instead. --- turbo.json | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/turbo.json b/turbo.json index 0872e7cc..421e242b 100644 --- a/turbo.json +++ b/turbo.json @@ -22,6 +22,19 @@ "dependsOn": ["^build", "build"], "outputs": [] }, + "@openora/testing#test:integration": { + "dependsOn": ["^build", "build", "@openora/core#test:integration"], + "cache": false, + "passThroughEnv": [ + "TEST_DATABASE_URL", + "DATABASE_URL", + "DATABASE_ADMIN_URL", + "TEST_ADMIN_DATABASE_URL", + "TEST_REDIS_URL", + "BETTER_AUTH_SECRET", + "BETTER_AUTH_URL" + ] + }, "test:integration": { "dependsOn": ["^build", "build"], "cache": false, From cc7d35221ec081f03bb40fbf0751cb313729276c Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 15:59:38 +0200 Subject: [PATCH 06/17] test: log funnel payload to diagnose the ci-only analytics failure --- packages/testing/src/__tests__/analytics.e2e.test.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/testing/src/__tests__/analytics.e2e.test.ts b/packages/testing/src/__tests__/analytics.e2e.test.ts index c86b15bf..c0a59773 100644 --- a/packages/testing/src/__tests__/analytics.e2e.test.ts +++ b/packages/testing/src/__tests__/analytics.e2e.test.ts @@ -167,6 +167,7 @@ describe('analytics e2e', () => { dropOffRate: number; }>; + console.log('FUNNEL_DEBUG', JSON.stringify(stages)); const byStage = new Map(stages.map((s) => [s.stage, s])); const registered = byStage.get('registered'); const emailVerified = byStage.get('email_verified'); From bf794a98b2811a7fc5cb3e8e70325cb4ecef30d4 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 16:04:15 +0200 Subject: [PATCH 07/17] test: log funnel service inputs in ci --- packages/core/src/analytics/service/funnel-analytics.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/analytics/service/funnel-analytics.service.ts b/packages/core/src/analytics/service/funnel-analytics.service.ts index 17ddd82d..062a332c 100644 --- a/packages/core/src/analytics/service/funnel-analytics.service.ts +++ b/packages/core/src/analytics/service/funnel-analytics.service.ts @@ -84,6 +84,7 @@ export class FunnelAnalyticsService { first_bet: Number(row?.first_bet ?? 0), }; + console.log('FUNNEL_SRV', JSON.stringify({ row, counts, stages: FUNNEL_STAGES, len: FUNNEL_STAGES.length })); return FUNNEL_STAGES.map((stage, index) => { const previousStage = FUNNEL_STAGES[index - 1]; return { From b58f383ddb94ac016eeda836ec460d6759dc136d Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 16:08:09 +0200 Subject: [PATCH 08/17] test: format debug logging --- .../core/src/analytics/service/funnel-analytics.service.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/core/src/analytics/service/funnel-analytics.service.ts b/packages/core/src/analytics/service/funnel-analytics.service.ts index 062a332c..fcf42501 100644 --- a/packages/core/src/analytics/service/funnel-analytics.service.ts +++ b/packages/core/src/analytics/service/funnel-analytics.service.ts @@ -84,7 +84,10 @@ export class FunnelAnalyticsService { first_bet: Number(row?.first_bet ?? 0), }; - console.log('FUNNEL_SRV', JSON.stringify({ row, counts, stages: FUNNEL_STAGES, len: FUNNEL_STAGES.length })); + console.log( + 'FUNNEL_SRV', + JSON.stringify({ row, counts, stages: FUNNEL_STAGES, len: FUNNEL_STAGES.length }), + ); return FUNNEL_STAGES.map((stage, index) => { const previousStage = FUNNEL_STAGES[index - 1]; return { From 06f597ba42980df0a9a25168a919fc7a58d08e51 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 16:13:19 +0200 Subject: [PATCH 09/17] test: log funnel service output in ci --- .../src/analytics/service/funnel-analytics.service.ts | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/packages/core/src/analytics/service/funnel-analytics.service.ts b/packages/core/src/analytics/service/funnel-analytics.service.ts index fcf42501..1d0f4e9f 100644 --- a/packages/core/src/analytics/service/funnel-analytics.service.ts +++ b/packages/core/src/analytics/service/funnel-analytics.service.ts @@ -84,11 +84,7 @@ export class FunnelAnalyticsService { first_bet: Number(row?.first_bet ?? 0), }; - console.log( - 'FUNNEL_SRV', - JSON.stringify({ row, counts, stages: FUNNEL_STAGES, len: FUNNEL_STAGES.length }), - ); - return FUNNEL_STAGES.map((stage, index) => { + const out = FUNNEL_STAGES.map((stage, index) => { const previousStage = FUNNEL_STAGES[index - 1]; return { stage, @@ -96,5 +92,7 @@ export class FunnelAnalyticsService { dropOffRate: previousStage ? dropOffRate(counts[previousStage], counts[stage]) : 0, }; }); + console.log('FUNNEL_OUT', JSON.stringify(out), out.map((s) => String(s.dropOffRate)).join('|')); + return out; } } From d4990a6e48cd9b2e3b1592780f4cad5d5cfe4ba4 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 16:18:11 +0200 Subject: [PATCH 10/17] test: dump compiled funnel source in ci --- .../core/src/analytics/service/funnel-analytics.service.ts | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/packages/core/src/analytics/service/funnel-analytics.service.ts b/packages/core/src/analytics/service/funnel-analytics.service.ts index 1d0f4e9f..39c89e08 100644 --- a/packages/core/src/analytics/service/funnel-analytics.service.ts +++ b/packages/core/src/analytics/service/funnel-analytics.service.ts @@ -92,7 +92,12 @@ export class FunnelAnalyticsService { dropOffRate: previousStage ? dropOffRate(counts[previousStage], counts[stage]) : 0, }; }); - console.log('FUNNEL_OUT', JSON.stringify(out), out.map((s) => String(s.dropOffRate)).join('|')); + console.log('FUNNEL_OUT', out.map((s) => String(s.dropOffRate)).join('|')); + console.log( + 'FUNNEL_SRC', + String(FunnelAnalyticsService.prototype['computeConversion']).slice(-400), + ); + console.log('FUNNEL_TS', process.env['npm_package_devDependencies_typescript'] ?? 'n/a'); return out; } } From 4b631b9117b7b140ad78b117c33d8f010b2ceb85 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 17:48:26 +0200 Subject: [PATCH 11/17] test: log funnel module url in ci --- packages/core/src/analytics/service/funnel-analytics.service.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/core/src/analytics/service/funnel-analytics.service.ts b/packages/core/src/analytics/service/funnel-analytics.service.ts index 39c89e08..a941ed8b 100644 --- a/packages/core/src/analytics/service/funnel-analytics.service.ts +++ b/packages/core/src/analytics/service/funnel-analytics.service.ts @@ -97,7 +97,7 @@ export class FunnelAnalyticsService { 'FUNNEL_SRC', String(FunnelAnalyticsService.prototype['computeConversion']).slice(-400), ); - console.log('FUNNEL_TS', process.env['npm_package_devDependencies_typescript'] ?? 'n/a'); + return out; } } From d8ca589bbda1325fa0b2c84d3d613b0bfdfa54e4 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 17:48:50 +0200 Subject: [PATCH 12/17] test: log funnel module url in ci --- packages/core/src/analytics/service/funnel-analytics.service.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/core/src/analytics/service/funnel-analytics.service.ts b/packages/core/src/analytics/service/funnel-analytics.service.ts index a941ed8b..bcdd1bec 100644 --- a/packages/core/src/analytics/service/funnel-analytics.service.ts +++ b/packages/core/src/analytics/service/funnel-analytics.service.ts @@ -93,6 +93,7 @@ export class FunnelAnalyticsService { }; }); console.log('FUNNEL_OUT', out.map((s) => String(s.dropOffRate)).join('|')); + console.log('FUNNEL_WHERE', import.meta.url); console.log( 'FUNNEL_SRC', String(FunnelAnalyticsService.prototype['computeConversion']).slice(-400), From 2de792a455090927eb62aadc83eac34d43eb3dbe Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 17:54:22 +0200 Subject: [PATCH 13/17] revert: back to typescript 6.0.3 tsc 7.0.2 miscompiles a ternary's numeric-literal branch in the linux CI build: analytics' `dropOffRate: previousStage ? rate(...) : 0` lands in dist as `: null`, so the funnel route serves null and the analytics e2e fails. The mac build of the same version emits 0 correctly, so it is platform-dependent - not worth carrying until it is understood upstream. --- apps/mcp-server-dev/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- .../service/funnel-analytics.service.ts | 10 +- packages/create-openora/package.json | 2 +- packages/mcp/package.json | 2 +- packages/testing/package.json | 2 +- .../src/__tests__/analytics.e2e.test.ts | 1 - pnpm-lock.yaml | 289 +++--------------- 9 files changed, 51 insertions(+), 261 deletions(-) diff --git a/apps/mcp-server-dev/package.json b/apps/mcp-server-dev/package.json index a1ec3894..21e246ba 100644 --- a/apps/mcp-server-dev/package.json +++ b/apps/mcp-server-dev/package.json @@ -19,6 +19,6 @@ "devDependencies": { "@types/node": "25.9.2", "@types/pg": "8.20.0", - "typescript": "7.0.2" + "typescript": "6.0.3" } } diff --git a/package.json b/package.json index 42df91a5..70617b0a 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "rulesync": "15.1.0", "tsx": "4.23.1", "turbo": "2.10.7", - "typescript": "7.0.2" + "typescript": "6.0.3" }, "engines": { "node": ">=26.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 243624e0..68ebf727 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -590,7 +590,7 @@ "@types/react": "19.2.17", "@types/react-dom": "19.2.3", "drizzle-kit": "0.31.10", - "typescript": "7.0.2", + "typescript": "6.0.3", "vite-tsconfig-paths": "6.1.1", "vitest": "4.1.8" }, diff --git a/packages/core/src/analytics/service/funnel-analytics.service.ts b/packages/core/src/analytics/service/funnel-analytics.service.ts index bcdd1bec..17ddd82d 100644 --- a/packages/core/src/analytics/service/funnel-analytics.service.ts +++ b/packages/core/src/analytics/service/funnel-analytics.service.ts @@ -84,7 +84,7 @@ export class FunnelAnalyticsService { first_bet: Number(row?.first_bet ?? 0), }; - const out = FUNNEL_STAGES.map((stage, index) => { + return FUNNEL_STAGES.map((stage, index) => { const previousStage = FUNNEL_STAGES[index - 1]; return { stage, @@ -92,13 +92,5 @@ export class FunnelAnalyticsService { dropOffRate: previousStage ? dropOffRate(counts[previousStage], counts[stage]) : 0, }; }); - console.log('FUNNEL_OUT', out.map((s) => String(s.dropOffRate)).join('|')); - console.log('FUNNEL_WHERE', import.meta.url); - console.log( - 'FUNNEL_SRC', - String(FunnelAnalyticsService.prototype['computeConversion']).slice(-400), - ); - - return out; } } diff --git a/packages/create-openora/package.json b/packages/create-openora/package.json index bdccc316..5dacad60 100644 --- a/packages/create-openora/package.json +++ b/packages/create-openora/package.json @@ -36,7 +36,7 @@ "devDependencies": { "@openora/core": "workspace:*", "@types/node": "25.9.2", - "typescript": "7.0.2" + "typescript": "6.0.3" }, "engines": { "node": ">=26" diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 6ca5b5a3..8650b8fd 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -38,6 +38,6 @@ }, "devDependencies": { "@types/node": "26.1.1", - "typescript": "7.0.2" + "typescript": "6.0.3" } } diff --git a/packages/testing/package.json b/packages/testing/package.json index 5cd03dbe..932e899c 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -29,7 +29,7 @@ "devDependencies": { "@types/node": "25.9.2", "@types/pg": "8.20.0", - "typescript": "7.0.2", + "typescript": "6.0.3", "vitest": "4.1.8" } } diff --git a/packages/testing/src/__tests__/analytics.e2e.test.ts b/packages/testing/src/__tests__/analytics.e2e.test.ts index c0a59773..c86b15bf 100644 --- a/packages/testing/src/__tests__/analytics.e2e.test.ts +++ b/packages/testing/src/__tests__/analytics.e2e.test.ts @@ -167,7 +167,6 @@ describe('analytics e2e', () => { dropOffRate: number; }>; - console.log('FUNNEL_DEBUG', JSON.stringify(stages)); const byStage = new Map(stages.map((s) => [s.stage, s])); const registered = byStage.get('registered'); const emailVerified = byStage.get('email_verified'); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef8f2dad..b03de238 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,7 +20,7 @@ importers: version: 2.31.1(@types/node@26.1.1) '@commitlint/cli': specifier: 21.2.1 - version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2) + version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@6.0.3) '@commitlint/config-conventional': specifier: 21.2.0 version: 21.2.0 @@ -59,7 +59,7 @@ importers: version: 8.22.0 rulesync: specifier: 15.1.0 - version: 15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@7.0.2)) + version: 15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@6.0.3)) tsx: specifier: 4.23.1 version: 4.23.1 @@ -67,8 +67,8 @@ importers: specifier: 2.10.7 version: 2.10.7 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 apps/mcp-server-dev: dependencies: @@ -92,8 +92,8 @@ importers: specifier: 8.20.0 version: 8.20.0 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 packages/core: dependencies: @@ -180,11 +180,11 @@ importers: specifier: 0.31.10 version: 0.31.10 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 vite-tsconfig-paths: specifier: 6.1.1 - version: 6.1.1(typescript@7.0.2)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) + version: 6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) vitest: specifier: 4.1.8 version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) @@ -198,8 +198,8 @@ importers: specifier: 25.9.2 version: 25.9.2 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 packages/mcp: dependencies: @@ -217,8 +217,8 @@ importers: specifier: 26.1.1 version: 26.1.1 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 packages/testing: dependencies: @@ -245,8 +245,8 @@ importers: specifier: 8.20.0 version: 8.20.0 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 vitest: specifier: 4.1.8 version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) @@ -1775,126 +1775,6 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} - '@typescript/typescript-aix-ppc64@7.0.2': - resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [aix] - - '@typescript/typescript-darwin-arm64@7.0.2': - resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [darwin] - - '@typescript/typescript-darwin-x64@7.0.2': - resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [darwin] - - '@typescript/typescript-freebsd-arm64@7.0.2': - resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [freebsd] - - '@typescript/typescript-freebsd-x64@7.0.2': - resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [freebsd] - - '@typescript/typescript-linux-arm64@7.0.2': - resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [linux] - - '@typescript/typescript-linux-arm@7.0.2': - resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} - engines: {node: '>=16.20.0'} - cpu: [arm] - os: [linux] - - '@typescript/typescript-linux-loong64@7.0.2': - resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} - engines: {node: '>=16.20.0'} - cpu: [loong64] - os: [linux] - - '@typescript/typescript-linux-mips64el@7.0.2': - resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} - engines: {node: '>=16.20.0'} - cpu: [mips64el] - os: [linux] - - '@typescript/typescript-linux-ppc64@7.0.2': - resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [linux] - - '@typescript/typescript-linux-riscv64@7.0.2': - resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} - engines: {node: '>=16.20.0'} - cpu: [riscv64] - os: [linux] - - '@typescript/typescript-linux-s390x@7.0.2': - resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} - engines: {node: '>=16.20.0'} - cpu: [s390x] - os: [linux] - - '@typescript/typescript-linux-x64@7.0.2': - resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [linux] - - '@typescript/typescript-netbsd-arm64@7.0.2': - resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [netbsd] - - '@typescript/typescript-netbsd-x64@7.0.2': - resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [netbsd] - - '@typescript/typescript-openbsd-arm64@7.0.2': - resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [openbsd] - - '@typescript/typescript-openbsd-x64@7.0.2': - resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [openbsd] - - '@typescript/typescript-sunos-x64@7.0.2': - resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [sunos] - - '@typescript/typescript-win32-arm64@7.0.2': - resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [win32] - - '@typescript/typescript-win32-x64@7.0.2': - resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [win32] - '@valibot/to-json-schema@1.7.1': resolution: {integrity: sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==} peerDependencies: @@ -3887,9 +3767,9 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript@7.0.2: - resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} - engines: {node: '>=16.20.0'} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} hasBin: true uint8array-extras@1.5.0: @@ -4357,12 +4237,12 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2)': + '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@6.0.3)': dependencies: '@commitlint/config-conventional': 21.2.0 '@commitlint/format': 21.2.0 '@commitlint/lint': 21.2.0 - '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@7.0.2) + '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@6.0.3) '@commitlint/read': 21.2.1(conventional-commits-parser@7.1.0) '@commitlint/types': 21.2.0 tinyexec: 1.2.4 @@ -4407,14 +4287,14 @@ snapshots: '@commitlint/rules': 21.2.0 '@commitlint/types': 21.2.0 - '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@7.0.2)': + '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@6.0.3)': dependencies: '@commitlint/config-validator': 21.2.0 '@commitlint/execute-rule': 21.0.1 '@commitlint/resolve-extends': 21.2.0 '@commitlint/types': 21.2.0 - cosmiconfig: 9.0.2(typescript@7.0.2) - cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2) + cosmiconfig: 9.0.2(typescript@6.0.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) es-toolkit: 1.48.1 is-plain-obj: 4.1.0 picocolors: 1.1.1 @@ -5613,69 +5493,9 @@ snapshots: dependencies: csstype: 3.2.3 - '@typescript/typescript-aix-ppc64@7.0.2': - optional: true - - '@typescript/typescript-darwin-arm64@7.0.2': - optional: true - - '@typescript/typescript-darwin-x64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-x64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm@7.0.2': - optional: true - - '@typescript/typescript-linux-loong64@7.0.2': - optional: true - - '@typescript/typescript-linux-mips64el@7.0.2': - optional: true - - '@typescript/typescript-linux-ppc64@7.0.2': - optional: true - - '@typescript/typescript-linux-riscv64@7.0.2': - optional: true - - '@typescript/typescript-linux-s390x@7.0.2': - optional: true - - '@typescript/typescript-linux-x64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-sunos-x64@7.0.2': - optional: true - - '@typescript/typescript-win32-arm64@7.0.2': - optional: true - - '@typescript/typescript-win32-x64@7.0.2': - optional: true - - '@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2))': + '@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3))': dependencies: - valibot: 1.4.1(typescript@7.0.2) + valibot: 1.4.1(typescript@6.0.3) '@vitest/coverage-v8@4.1.8(vitest@4.1.8)': dependencies: @@ -6011,21 +5831,21 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2): + cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): dependencies: '@types/node': 26.1.1 - cosmiconfig: 9.0.2(typescript@7.0.2) + cosmiconfig: 9.0.2(typescript@6.0.3) jiti: 2.6.1 - typescript: 7.0.2 + typescript: 6.0.3 - cosmiconfig@9.0.2(typescript@7.0.2): + cosmiconfig@9.0.2(typescript@6.0.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.2.0 parse-json: 5.2.0 optionalDependencies: - typescript: 7.0.2 + typescript: 6.0.3 cron-parser@4.9.0: dependencies: @@ -6301,7 +6121,7 @@ snapshots: fast-uri@3.1.2: {} - fastmcp@4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4): + fastmcp@4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@standard-schema/spec': 1.1.0 @@ -6313,7 +6133,7 @@ snapshots: strict-event-emitter-types: 2.0.0 undici: 7.28.0 uri-templates: 0.2.0 - xsschema: 0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) + xsschema: 0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) yargs: 18.0.0 zod: 4.4.3 zod-to-json-schema: 3.25.2(zod@4.4.3) @@ -7281,17 +7101,17 @@ snapshots: transitivePeerDependencies: - supports-color - rulesync@15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@7.0.2)): + rulesync@15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@6.0.3)): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@octokit/request-error': 7.1.0 '@octokit/rest': 22.0.1 '@toon-format/toon': 4.0.0 - '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@7.0.2)) + '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@6.0.3)) commander: 15.0.0 effect: 3.22.0 es-toolkit: 1.49.0 - fastmcp: 4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4) + fastmcp: 4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4) globby: 16.2.2 gray-matter: 4.0.3 js-yaml: 5.2.1 @@ -7557,9 +7377,9 @@ snapshots: '@ts-morph/common': 0.29.0 code-block-writer: 13.0.3 - tsconfck@3.1.6(typescript@7.0.2): + tsconfck@3.1.6(typescript@6.0.3): optionalDependencies: - typescript: 7.0.2 + typescript: 6.0.3 tsconfig-paths-webpack-plugin@4.2.0: dependencies: @@ -7603,28 +7423,7 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript@7.0.2: - optionalDependencies: - '@typescript/typescript-aix-ppc64': 7.0.2 - '@typescript/typescript-darwin-arm64': 7.0.2 - '@typescript/typescript-darwin-x64': 7.0.2 - '@typescript/typescript-freebsd-arm64': 7.0.2 - '@typescript/typescript-freebsd-x64': 7.0.2 - '@typescript/typescript-linux-arm': 7.0.2 - '@typescript/typescript-linux-arm64': 7.0.2 - '@typescript/typescript-linux-loong64': 7.0.2 - '@typescript/typescript-linux-mips64el': 7.0.2 - '@typescript/typescript-linux-ppc64': 7.0.2 - '@typescript/typescript-linux-riscv64': 7.0.2 - '@typescript/typescript-linux-s390x': 7.0.2 - '@typescript/typescript-linux-x64': 7.0.2 - '@typescript/typescript-netbsd-arm64': 7.0.2 - '@typescript/typescript-netbsd-x64': 7.0.2 - '@typescript/typescript-openbsd-arm64': 7.0.2 - '@typescript/typescript-openbsd-x64': 7.0.2 - '@typescript/typescript-sunos-x64': 7.0.2 - '@typescript/typescript-win32-arm64': 7.0.2 - '@typescript/typescript-win32-x64': 7.0.2 + typescript@6.0.3: {} uint8array-extras@1.5.0: {} @@ -7646,17 +7445,17 @@ snapshots: uri-templates@0.2.0: {} - valibot@1.4.1(typescript@7.0.2): + valibot@1.4.1(typescript@6.0.3): optionalDependencies: - typescript: 7.0.2 + typescript: 6.0.3 vary@1.1.2: {} - vite-tsconfig-paths@6.1.1(typescript@7.0.2)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)): + vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)): dependencies: debug: 4.4.3 globrex: 0.1.2 - tsconfck: 3.1.6(typescript@7.0.2) + tsconfck: 3.1.6(typescript@6.0.3) vite: 8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1) transitivePeerDependencies: - supports-color @@ -7734,9 +7533,9 @@ snapshots: ws@8.21.1: optional: true - xsschema@0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3): + xsschema@0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3): optionalDependencies: - '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@7.0.2)) + '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@6.0.3)) effect: 3.22.0 sury: 10.0.4 zod: 4.4.3 From 0942ecf2f427f447120a9c61a7b1178c94d0678b Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Thu, 30 Jul 2026 18:02:40 +0200 Subject: [PATCH 14/17] fix(analytics): assert the nullable first-stage drop-off rate in the e2e 167ea67c made the first funnel stage return null and the contract nullable but left the e2e asserting 0. Nothing caught it because verify never ran this suite until test:integration joined the gate. Restores typescript 7.0.2 too - the null came from dev, not from the compiler. --- apps/mcp-server-dev/package.json | 2 +- package.json | 2 +- packages/core/package.json | 2 +- packages/create-openora/package.json | 2 +- packages/mcp/package.json | 2 +- packages/testing/package.json | 2 +- .../src/__tests__/analytics.e2e.test.ts | 4 +- pnpm-lock.yaml | 289 +++++++++++++++--- 8 files changed, 253 insertions(+), 52 deletions(-) diff --git a/apps/mcp-server-dev/package.json b/apps/mcp-server-dev/package.json index 21e246ba..a1ec3894 100644 --- a/apps/mcp-server-dev/package.json +++ b/apps/mcp-server-dev/package.json @@ -19,6 +19,6 @@ "devDependencies": { "@types/node": "25.9.2", "@types/pg": "8.20.0", - "typescript": "6.0.3" + "typescript": "7.0.2" } } diff --git a/package.json b/package.json index 70617b0a..42df91a5 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "rulesync": "15.1.0", "tsx": "4.23.1", "turbo": "2.10.7", - "typescript": "6.0.3" + "typescript": "7.0.2" }, "engines": { "node": ">=26.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 68ebf727..243624e0 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -590,7 +590,7 @@ "@types/react": "19.2.17", "@types/react-dom": "19.2.3", "drizzle-kit": "0.31.10", - "typescript": "6.0.3", + "typescript": "7.0.2", "vite-tsconfig-paths": "6.1.1", "vitest": "4.1.8" }, diff --git a/packages/create-openora/package.json b/packages/create-openora/package.json index 5dacad60..bdccc316 100644 --- a/packages/create-openora/package.json +++ b/packages/create-openora/package.json @@ -36,7 +36,7 @@ "devDependencies": { "@openora/core": "workspace:*", "@types/node": "25.9.2", - "typescript": "6.0.3" + "typescript": "7.0.2" }, "engines": { "node": ">=26" diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 8650b8fd..6ca5b5a3 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -38,6 +38,6 @@ }, "devDependencies": { "@types/node": "26.1.1", - "typescript": "6.0.3" + "typescript": "7.0.2" } } diff --git a/packages/testing/package.json b/packages/testing/package.json index 932e899c..5cd03dbe 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -29,7 +29,7 @@ "devDependencies": { "@types/node": "25.9.2", "@types/pg": "8.20.0", - "typescript": "6.0.3", + "typescript": "7.0.2", "vitest": "4.1.8" } } diff --git a/packages/testing/src/__tests__/analytics.e2e.test.ts b/packages/testing/src/__tests__/analytics.e2e.test.ts index c86b15bf..4d6ab17e 100644 --- a/packages/testing/src/__tests__/analytics.e2e.test.ts +++ b/packages/testing/src/__tests__/analytics.e2e.test.ts @@ -164,7 +164,7 @@ describe('analytics e2e', () => { const stages = (await readJson(res)) as Array<{ stage: string; count: number; - dropOffRate: number; + dropOffRate: number | null; }>; const byStage = new Map(stages.map((s) => [s.stage, s])); @@ -183,7 +183,7 @@ describe('analytics e2e', () => { expect(firstDeposit?.count).toBeLessThanOrEqual(emailVerified?.count ?? 0); expect(firstBet?.count).toBeLessThanOrEqual(firstDeposit?.count ?? 0); expect(firstBet?.count).toBeGreaterThanOrEqual(1); - expect(registered?.dropOffRate).toBe(0); + expect(registered?.dropOffRate).toBeNull(); }); it('rejects an analytics read from a non-admin caller', async () => { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b03de238..ef8f2dad 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,7 +20,7 @@ importers: version: 2.31.1(@types/node@26.1.1) '@commitlint/cli': specifier: 21.2.1 - version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@6.0.3) + version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2) '@commitlint/config-conventional': specifier: 21.2.0 version: 21.2.0 @@ -59,7 +59,7 @@ importers: version: 8.22.0 rulesync: specifier: 15.1.0 - version: 15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@6.0.3)) + version: 15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@7.0.2)) tsx: specifier: 4.23.1 version: 4.23.1 @@ -67,8 +67,8 @@ importers: specifier: 2.10.7 version: 2.10.7 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 apps/mcp-server-dev: dependencies: @@ -92,8 +92,8 @@ importers: specifier: 8.20.0 version: 8.20.0 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 packages/core: dependencies: @@ -180,11 +180,11 @@ importers: specifier: 0.31.10 version: 0.31.10 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 vite-tsconfig-paths: specifier: 6.1.1 - version: 6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) + version: 6.1.1(typescript@7.0.2)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) vitest: specifier: 4.1.8 version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) @@ -198,8 +198,8 @@ importers: specifier: 25.9.2 version: 25.9.2 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 packages/mcp: dependencies: @@ -217,8 +217,8 @@ importers: specifier: 26.1.1 version: 26.1.1 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 packages/testing: dependencies: @@ -245,8 +245,8 @@ importers: specifier: 8.20.0 version: 8.20.0 typescript: - specifier: 6.0.3 - version: 6.0.3 + specifier: 7.0.2 + version: 7.0.2 vitest: specifier: 4.1.8 version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) @@ -1775,6 +1775,126 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} + '@typescript/typescript-aix-ppc64@7.0.2': + resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [aix] + + '@typescript/typescript-darwin-arm64@7.0.2': + resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [darwin] + + '@typescript/typescript-darwin-x64@7.0.2': + resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [darwin] + + '@typescript/typescript-freebsd-arm64@7.0.2': + resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [freebsd] + + '@typescript/typescript-freebsd-x64@7.0.2': + resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [freebsd] + + '@typescript/typescript-linux-arm64@7.0.2': + resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [linux] + + '@typescript/typescript-linux-arm@7.0.2': + resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} + engines: {node: '>=16.20.0'} + cpu: [arm] + os: [linux] + + '@typescript/typescript-linux-loong64@7.0.2': + resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} + engines: {node: '>=16.20.0'} + cpu: [loong64] + os: [linux] + + '@typescript/typescript-linux-mips64el@7.0.2': + resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} + engines: {node: '>=16.20.0'} + cpu: [mips64el] + os: [linux] + + '@typescript/typescript-linux-ppc64@7.0.2': + resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} + engines: {node: '>=16.20.0'} + cpu: [ppc64] + os: [linux] + + '@typescript/typescript-linux-riscv64@7.0.2': + resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} + engines: {node: '>=16.20.0'} + cpu: [riscv64] + os: [linux] + + '@typescript/typescript-linux-s390x@7.0.2': + resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} + engines: {node: '>=16.20.0'} + cpu: [s390x] + os: [linux] + + '@typescript/typescript-linux-x64@7.0.2': + resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [linux] + + '@typescript/typescript-netbsd-arm64@7.0.2': + resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [netbsd] + + '@typescript/typescript-netbsd-x64@7.0.2': + resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [netbsd] + + '@typescript/typescript-openbsd-arm64@7.0.2': + resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [openbsd] + + '@typescript/typescript-openbsd-x64@7.0.2': + resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [openbsd] + + '@typescript/typescript-sunos-x64@7.0.2': + resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [sunos] + + '@typescript/typescript-win32-arm64@7.0.2': + resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} + engines: {node: '>=16.20.0'} + cpu: [arm64] + os: [win32] + + '@typescript/typescript-win32-x64@7.0.2': + resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} + engines: {node: '>=16.20.0'} + cpu: [x64] + os: [win32] + '@valibot/to-json-schema@1.7.1': resolution: {integrity: sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==} peerDependencies: @@ -3767,9 +3887,9 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript@6.0.3: - resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} - engines: {node: '>=14.17'} + typescript@7.0.2: + resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} + engines: {node: '>=16.20.0'} hasBin: true uint8array-extras@1.5.0: @@ -4237,12 +4357,12 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@6.0.3)': + '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2)': dependencies: '@commitlint/config-conventional': 21.2.0 '@commitlint/format': 21.2.0 '@commitlint/lint': 21.2.0 - '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@6.0.3) + '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@7.0.2) '@commitlint/read': 21.2.1(conventional-commits-parser@7.1.0) '@commitlint/types': 21.2.0 tinyexec: 1.2.4 @@ -4287,14 +4407,14 @@ snapshots: '@commitlint/rules': 21.2.0 '@commitlint/types': 21.2.0 - '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@6.0.3)': + '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@7.0.2)': dependencies: '@commitlint/config-validator': 21.2.0 '@commitlint/execute-rule': 21.0.1 '@commitlint/resolve-extends': 21.2.0 '@commitlint/types': 21.2.0 - cosmiconfig: 9.0.2(typescript@6.0.3) - cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) + cosmiconfig: 9.0.2(typescript@7.0.2) + cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2) es-toolkit: 1.48.1 is-plain-obj: 4.1.0 picocolors: 1.1.1 @@ -5493,9 +5613,69 @@ snapshots: dependencies: csstype: 3.2.3 - '@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3))': + '@typescript/typescript-aix-ppc64@7.0.2': + optional: true + + '@typescript/typescript-darwin-arm64@7.0.2': + optional: true + + '@typescript/typescript-darwin-x64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-freebsd-x64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm64@7.0.2': + optional: true + + '@typescript/typescript-linux-arm@7.0.2': + optional: true + + '@typescript/typescript-linux-loong64@7.0.2': + optional: true + + '@typescript/typescript-linux-mips64el@7.0.2': + optional: true + + '@typescript/typescript-linux-ppc64@7.0.2': + optional: true + + '@typescript/typescript-linux-riscv64@7.0.2': + optional: true + + '@typescript/typescript-linux-s390x@7.0.2': + optional: true + + '@typescript/typescript-linux-x64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-netbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-arm64@7.0.2': + optional: true + + '@typescript/typescript-openbsd-x64@7.0.2': + optional: true + + '@typescript/typescript-sunos-x64@7.0.2': + optional: true + + '@typescript/typescript-win32-arm64@7.0.2': + optional: true + + '@typescript/typescript-win32-x64@7.0.2': + optional: true + + '@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2))': dependencies: - valibot: 1.4.1(typescript@6.0.3) + valibot: 1.4.1(typescript@7.0.2) '@vitest/coverage-v8@4.1.8(vitest@4.1.8)': dependencies: @@ -5831,21 +6011,21 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): + cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2): dependencies: '@types/node': 26.1.1 - cosmiconfig: 9.0.2(typescript@6.0.3) + cosmiconfig: 9.0.2(typescript@7.0.2) jiti: 2.6.1 - typescript: 6.0.3 + typescript: 7.0.2 - cosmiconfig@9.0.2(typescript@6.0.3): + cosmiconfig@9.0.2(typescript@7.0.2): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.2.0 parse-json: 5.2.0 optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 cron-parser@4.9.0: dependencies: @@ -6121,7 +6301,7 @@ snapshots: fast-uri@3.1.2: {} - fastmcp@4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4): + fastmcp@4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@standard-schema/spec': 1.1.0 @@ -6133,7 +6313,7 @@ snapshots: strict-event-emitter-types: 2.0.0 undici: 7.28.0 uri-templates: 0.2.0 - xsschema: 0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) + xsschema: 0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) yargs: 18.0.0 zod: 4.4.3 zod-to-json-schema: 3.25.2(zod@4.4.3) @@ -7101,17 +7281,17 @@ snapshots: transitivePeerDependencies: - supports-color - rulesync@15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@6.0.3)): + rulesync@15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@7.0.2)): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@octokit/request-error': 7.1.0 '@octokit/rest': 22.0.1 '@toon-format/toon': 4.0.0 - '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@6.0.3)) + '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@7.0.2)) commander: 15.0.0 effect: 3.22.0 es-toolkit: 1.49.0 - fastmcp: 4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4) + fastmcp: 4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4) globby: 16.2.2 gray-matter: 4.0.3 js-yaml: 5.2.1 @@ -7377,9 +7557,9 @@ snapshots: '@ts-morph/common': 0.29.0 code-block-writer: 13.0.3 - tsconfck@3.1.6(typescript@6.0.3): + tsconfck@3.1.6(typescript@7.0.2): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 tsconfig-paths-webpack-plugin@4.2.0: dependencies: @@ -7423,7 +7603,28 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript@6.0.3: {} + typescript@7.0.2: + optionalDependencies: + '@typescript/typescript-aix-ppc64': 7.0.2 + '@typescript/typescript-darwin-arm64': 7.0.2 + '@typescript/typescript-darwin-x64': 7.0.2 + '@typescript/typescript-freebsd-arm64': 7.0.2 + '@typescript/typescript-freebsd-x64': 7.0.2 + '@typescript/typescript-linux-arm': 7.0.2 + '@typescript/typescript-linux-arm64': 7.0.2 + '@typescript/typescript-linux-loong64': 7.0.2 + '@typescript/typescript-linux-mips64el': 7.0.2 + '@typescript/typescript-linux-ppc64': 7.0.2 + '@typescript/typescript-linux-riscv64': 7.0.2 + '@typescript/typescript-linux-s390x': 7.0.2 + '@typescript/typescript-linux-x64': 7.0.2 + '@typescript/typescript-netbsd-arm64': 7.0.2 + '@typescript/typescript-netbsd-x64': 7.0.2 + '@typescript/typescript-openbsd-arm64': 7.0.2 + '@typescript/typescript-openbsd-x64': 7.0.2 + '@typescript/typescript-sunos-x64': 7.0.2 + '@typescript/typescript-win32-arm64': 7.0.2 + '@typescript/typescript-win32-x64': 7.0.2 uint8array-extras@1.5.0: {} @@ -7445,17 +7646,17 @@ snapshots: uri-templates@0.2.0: {} - valibot@1.4.1(typescript@6.0.3): + valibot@1.4.1(typescript@7.0.2): optionalDependencies: - typescript: 6.0.3 + typescript: 7.0.2 vary@1.1.2: {} - vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)): + vite-tsconfig-paths@6.1.1(typescript@7.0.2)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)): dependencies: debug: 4.4.3 globrex: 0.1.2 - tsconfck: 3.1.6(typescript@6.0.3) + tsconfck: 3.1.6(typescript@7.0.2) vite: 8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1) transitivePeerDependencies: - supports-color @@ -7533,9 +7734,9 @@ snapshots: ws@8.21.1: optional: true - xsschema@0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3): + xsschema@0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3): optionalDependencies: - '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@6.0.3)) + '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@7.0.2)) effect: 3.22.0 sury: 10.0.4 zod: 4.4.3 From a3028397ef4f67d75d9d83075e85b8069473507a Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Fri, 31 Jul 2026 13:21:12 +0200 Subject: [PATCH 15/17] chore(rules): finish split and test tiers --- .rulesync/rules/conventions.md | 29 +- .rulesync/rules/db-conventions.md | 103 +------ .rulesync/rules/overview.md | 2 +- apps/mcp-server-dev/package.json | 2 +- docs/standards/database.md | 81 +++++ package.json | 2 +- packages/core/package.json | 2 +- ...test.ts => backoffice.service.int.test.ts} | 0 ...ng.test.ts => admin-reporting.int.test.ts} | 0 ...t.ts => admin-player-activity.int.test.ts} | 0 packages/create-openora/package.json | 2 +- packages/mcp/package.json | 2 +- packages/testing/package.json | 2 +- pnpm-lock.yaml | 289 +++--------------- 14 files changed, 151 insertions(+), 365 deletions(-) create mode 100644 docs/standards/database.md rename packages/core/src/admin-console/__tests__/{backoffice.service.test.ts => backoffice.service.int.test.ts} (100%) rename packages/core/src/casino/gaming/__tests__/{admin-reporting.test.ts => admin-reporting.int.test.ts} (100%) rename packages/core/src/iam/__tests__/{admin-player-activity.test.ts => admin-player-activity.int.test.ts} (100%) diff --git a/.rulesync/rules/conventions.md b/.rulesync/rules/conventions.md index cea89a36..e1a0db46 100644 --- a/.rulesync/rules/conventions.md +++ b/.rulesync/rules/conventions.md @@ -11,19 +11,20 @@ description: Engineering code conventions (TS, headless backend) - apply to ever # Engineering Conventions -The always-on core of the code standard: what you must obey while typing. Detail, examples and rationale live in `docs/standards/` - read the one file that matches the change instead of carrying all of it. Async seams: `messaging-and-microservices`. SQL / Drizzle: `db-conventions`. Repo map, decision tree, dependency rules: `overview`. - -| Change you are making | Read first | -| ------------------------------------- | ------------------------------------ | -| schema, type, enum-like value set | `docs/standards/types.md` | -| function, service method, constructor | `docs/standards/functions.md` | -| new module, DI wiring, integration | `docs/standards/module-structure.md` | -| error class, catch, money path | `docs/standards/errors.md` | -| a test | `docs/standards/testing.md` | -| a comment or JSDoc | `docs/standards/comments.md` | -| a hook / the typed client | `docs/standards/react-sdk.md` | -| commit, PR | `docs/standards/git-delivery.md` | -| a failing gate, a new lint rule | `docs/standards/enforcement.md` | +The always-on core of the code standard: what you must obey while typing. Detail, examples and rationale live in `docs/standards/` - read the one file that matches the change instead of carrying all of it. Async seams: `messaging-and-microservices`. SQL / Drizzle: `db-conventions`, then `docs/standards/database.md`. Repo map, decision tree, dependency rules: `overview`. + +| Change you are making | Read first | +| -------------------------------------- | ------------------------------------ | +| schema, type, enum-like value set | `docs/standards/types.md` | +| SQL, Drizzle, migration, seed, DB tool | `docs/standards/database.md` | +| function, service method, constructor | `docs/standards/functions.md` | +| new module, DI wiring, integration | `docs/standards/module-structure.md` | +| error class, catch, money path | `docs/standards/errors.md` | +| a test | `docs/standards/testing.md` | +| a comment or JSDoc | `docs/standards/comments.md` | +| a hook / the typed client | `docs/standards/react-sdk.md` | +| commit, PR | `docs/standards/git-delivery.md` | +| a failing gate, a new lint rule | `docs/standards/enforcement.md` | ## Philosophy @@ -39,7 +40,7 @@ The always-on core of the code standard: what you must obey while typing. Detail - `any` (tests included), `!` non-null assertions, `arr[i]!`, `as` casts to silence the compiler (`as const` is fine; test doubles go through the `mock` helper). - `interface`, TS `enum`, decorators, inheritance for reuse, default exports (except `plugin.ts` + `drizzle.config.ts`). - Hand-written duplicates of an inferrable type, re-inferring an imported schema, re-typing derived schema fields. -- Raw `z.uuid()` (use `UuidSchema`), inline `z.enum([...])` outside a contract dir, inline `pgEnum` value arrays. +- Raw `z.uuid()` (use `UuidSchema`), inline `z.enum([...])` outside a contract dir. - Inline `fetch`/`axios` in module code - third-party access is a port + adapter bound at the root. - Comments. The only exception is a fact the code cannot contain (external-system behaviour, a spec constraint) and JSDoc on a public export. A rationale is not a fact - it goes in the commit or an ADR. - Deep (`../../`+) relative imports that leave your zone/module, imports of another module's internals, import cycles, deep `dist/`/`src/` paths into another package. diff --git a/.rulesync/rules/db-conventions.md b/.rulesync/rules/db-conventions.md index b2bfb68c..509cbb5e 100644 --- a/.rulesync/rules/db-conventions.md +++ b/.rulesync/rules/db-conventions.md @@ -2,9 +2,6 @@ root: false targets: - '*' -# Scoped to where SQL is actually written/read: schema defs, services + adapters -# (queries), module-root directory/reporting impls (admin-*.ts), seeds, migrations, -# the engine db zone, the test harness, and db tooling - not react/contract/router edits. globs: - 'packages/**/schema/**' - 'packages/**/service/**' @@ -17,103 +14,11 @@ globs: - 'packages/core/src/server/db/**' - 'packages/testing/src/**' - 'tools/db/**' -description: SQL / Drizzle conventions - snake_case identifiers, timestamptz, keys, indexing, efficient ops, migrations. +description: SQL / Drizzle routing rule - read the database standard before editing matching files. --- -# Database conventions (SQL / Drizzle) +# Database conventions -The authoritative SQL rule for the platform. Tables live in a module's `src/schema/index.ts`; every -module owns its own `drizzle.config.ts` + co-located `drizzle/migrations/` history (ADR-0027). Layering -and DI live in `docs/standards/module-structure.md`; import boundaries in `overview` > Dependency rules. This is SQL only. +For every matching file, read [`docs/standards/database.md`](../../docs/standards/database.md) in full before editing. -## Enums - pgEnum derives from the contract tuple - -Never an inline value array (lint error `oss-module-shape/no-inline-pg-enum`). Declare the set once on the contract surface as a tuple + `z.enum` + inferred type (see `docs/standards/types.md`), then `pgEnum('x_status', X_STATUSES)` - the DB enum can never drift from the contract, and consumers import the same values. Reference: `wallet/schema/index.ts`. - -## Identifiers - snake_case everywhere - -Every drizzle instance + drizzle-kit config sets `casing: 'snake_case'`, so the SQL name derives from -the camelCase key. Pass an explicit name only where casing can't derive it: table names, `pgEnum` -types, and index/constraint names. - -```ts -// good - key derives the column; const camelCase; explicit snake_case only where needed -export const walletTransaction = pgTable( - 'wallet_transaction', - { - id: uuid().primaryKey().defaultRandom(), - walletId: uuid().notNull(), // -> wallet_id - type: walletTransactionType().notNull(), // pgEnum('wallet_transaction_type', ...) - amount: decimal({ precision: 18, scale: 2 }).notNull(), - createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(), - }, - (t) => [index('wallet_transaction_wallet_id_idx').on(t.walletId)], -); - -// bad - explicit/camelCase column names, PascalCase table -pgTable('WalletTransaction', { wallet_id: uuid('walletId') }); -``` - -Lint error `oss-module-shape/drizzle-snake-case` - a non-snake table/enum/index name or an explicit -camelCase column name. Row type is `typeof walletTransaction.$inferSelect`; never hand-write it. - -## Money - exact decimal, never float, never scaled integer - -Every money column is `decimal()` (Postgres `NUMERIC`) - never `real`/`float`, never an -`integer` "cents" column. Pair it with a `currency` column; on the wire, use the shared -`MoneyAmountSchema` (decimal string) + `currency`, never `z.number()`. Balance math runs -in SQL (`sql\`${wallet.balance} + ${amount}::numeric\``), never JS float arithmetic. Full -rationale (multi-currency exponents, crypto, wire-format precedent): ADR-0029. - -Lint error `oss-module-shape/no-float-money` bans a float-typed column in a schema file. - -## Timestamps - always timestamptz - -```ts -createdAt: timestamp({ withTimezone: true }); // good - Postgres timestamptz, store UTC -createdAt: timestamp(); // bad - naive, drops the zone -``` - -Applies to every datetime column (`createdAt`, `updatedAt`, `expiresAt`, any `*At`). Lint error -`oss-module-shape/no-naive-timestamp` flags a `timestamp()` missing `{ withTimezone: true }`. - -## Keys, references, indexes - -- UUID primary keys (`uuid().primaryKey().defaultRandom()`). -- **No foreign keys across a module boundary** - store a plain ID string and resolve via a - command port, event, or read-only `/schema` subpath. FKs only within the same module. -- `NOT NULL` by default; push defaults to the DB (`.notNull().default(...)`). -- Index every column you filter or join on; name it `__idx`. Run `propose-table-change` - (MCP) before adding a table to catch collisions. - -## Efficient operations - -```ts -// bad - N+1 -for (const id of ids) await db.select().from(wallet).where(eq(wallet.id, id)); -// good - one batched query -await db.select().from(wallet).where(inArray(wallet.id, ids)); -``` - -- Select only the columns you use; don't read wide rows for one field. -- **Bound the fan-out** - never `Promise.all(rows.map(fn))` when `rows` is a query result and `fn` - touches the DB: it opens one pool connection per row and starves every other query at scale. Use - `mapConcurrent(items, limit, fn)` (`@openora/core/server`). Lint: `oss-module-shape/no-unbounded-db-fanout`. -- **Money / critical paths are transactional and idempotent** - a DB guard inside the transaction, - not just an `idempotencyKey` (delivery is at-least-once). - -```ts -await db.transaction(async (t) => { - if (await ledgerExists(t, idempotencyKey)) return; // guard, not just a key - await insertLedger(t, { idempotencyKey, amount }); -}); -``` - -## Migrations - -- Never hand-edit generated migrations (or `docs/openapi.json` / `docs/catalog.json`). Change the - `pgTable`, then `pnpm regen` (drizzle-kit generates the migration + emits OpenAPI + catalog). -- Every module owns its own `drizzle/migrations/` + `__drizzle_migrations_` tracking table, - co-located with its schema (ADR-0027). One shared database, one journal per module. -- A Postgres extension an index needs (eg `pg_trgm`) goes in the module `migrate()`'s `extensions` - option, never hand-edited into a regenerated migration. +That file is the sole authoritative database and Drizzle standard. Do not duplicate its rules here. diff --git a/.rulesync/rules/overview.md b/.rulesync/rules/overview.md index 37bc8f28..87465790 100644 --- a/.rulesync/rules/overview.md +++ b/.rulesync/rules/overview.md @@ -10,7 +10,7 @@ globs: Canonical brief for AI agents and humans. Per-tool files (`AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`, `.codex/config.toml`, subagent + command mirrors) are generated by [rulesync](https://github.com/dyoshikawa/rulesync) from `.rulesync/`. Edit the source, run `pnpm gen:agents`. Never hand-edit generated files. -Sibling rules (load on demand; don't reopen settled questions): `conventions` (the always-on code standard, with a table routing to the deep-dive file in `docs/standards/`), `messaging-and-microservices` (async seams, command vs event vs job, outbox), `db-conventions` (SQL / Drizzle). Module layering, DI, ports and the shared-helper table live in `docs/standards/module-structure.md`. +Sibling rules (load on demand; do not reopen settled questions): `conventions` (the always-on code standard, with a table routing to the deep-dive file in `docs/standards/`), `messaging-and-microservices` (async seams, command vs event vs job, outbox), `db-conventions` (routes matching SQL / Drizzle files to `docs/standards/database.md`). Module layering, DI, ports and the shared-helper table live in `docs/standards/module-structure.md`. ## Mission diff --git a/apps/mcp-server-dev/package.json b/apps/mcp-server-dev/package.json index a1ec3894..21e246ba 100644 --- a/apps/mcp-server-dev/package.json +++ b/apps/mcp-server-dev/package.json @@ -19,6 +19,6 @@ "devDependencies": { "@types/node": "25.9.2", "@types/pg": "8.20.0", - "typescript": "7.0.2" + "typescript": "6.0.3" } } diff --git a/docs/standards/database.md b/docs/standards/database.md new file mode 100644 index 00000000..c81758ab --- /dev/null +++ b/docs/standards/database.md @@ -0,0 +1,81 @@ +# Database conventions (SQL / Drizzle) + +Read this in full before editing a schema, Drizzle query, migration configuration, seed, or database tool. Tables live in a module's `src/schema/index.ts`; every module owns its own `drizzle.config.ts` and co-located `drizzle/migrations/` history (ADR-0027). Layering and DI live in `docs/standards/module-structure.md`; import boundaries in `overview` > Dependency rules. + +## Enums - pgEnum derives from the contract tuple + +Never an inline value array (lint error `oss-module-shape/no-inline-pg-enum`). Declare the set once on the contract surface as a tuple + `z.enum` + inferred type (see `docs/standards/types.md`), then `pgEnum('x_status', X_STATUSES)` - the DB enum can never drift from the contract, and consumers import the same values. Reference: `wallet/schema/index.ts`. + +## Identifiers - snake_case everywhere + +Every Drizzle instance and drizzle-kit config sets `casing: 'snake_case'`, so the SQL name derives from the camelCase key. Pass an explicit name only where casing cannot derive it: table names, `pgEnum` types, and index/constraint names. + +The first declaration is valid; the second violates the naming rule. + +```ts +export const walletTransaction = pgTable( + 'wallet_transaction', + { + id: uuid().primaryKey().defaultRandom(), + walletId: uuid().notNull(), + type: walletTransactionType().notNull(), + amount: decimal({ precision: 18, scale: 2 }).notNull(), + createdAt: timestamp({ withTimezone: true }).notNull().defaultNow(), + }, + (t) => [index('wallet_transaction_wallet_id_idx').on(t.walletId)], +); + +pgTable('WalletTransaction', { wallet_id: uuid('walletId') }); +``` + +Lint error `oss-module-shape/drizzle-snake-case` rejects a non-snake table, enum, or index name and an explicit camelCase column name. Row type is `typeof walletTransaction.$inferSelect`; never hand-write it. + +## Money - exact decimal, never float, never scaled integer + +Every money column is `decimal()` (Postgres `NUMERIC`) - never `real`/`float`, never an `integer` "cents" column. Pair it with a `currency` column; on the wire, use the shared `MoneyAmountSchema` (decimal string) + `currency`, never `z.number()`. Balance math runs in SQL (`sql\`${wallet.balance} + ${amount}::numeric\``), never JS float arithmetic. Multi-currency exponents, crypto, and existing wire-format conventions require this. Full rationale: ADR-0029. + +Lint error `oss-module-shape/no-float-money` bans a float-typed column in a schema file. + +## Timestamps - always timestamptz + +The first declaration is valid; the second uses a naive timestamp and is invalid. + +```ts +createdAt: timestamp({ withTimezone: true }); +createdAt: timestamp(); +``` + +Applies to every datetime column (`createdAt`, `updatedAt`, `expiresAt`, any `*At`). Lint error `oss-module-shape/no-naive-timestamp` flags a `timestamp()` missing `{ withTimezone: true }`. + +## Keys, references, indexes + +- UUID primary keys (`uuid().primaryKey().defaultRandom()`). +- No foreign keys across a module boundary - store a plain ID string and resolve via a command port, event, or read-only `/schema` subpath. FKs only within the same module. +- `NOT NULL` by default; push defaults to the DB (`.notNull().default(...)`). +- Index every column you filter or join on; name it `
__idx`. Run `propose-table-change` before adding a table to catch collisions. + +## Efficient operations + +The first query is an N+1 anti-pattern; the second is the required batched query. + +```ts +for (const id of ids) await db.select().from(wallet).where(eq(wallet.id, id)); +await db.select().from(wallet).where(inArray(wallet.id, ids)); +``` + +- Select only the columns you use; do not read wide rows for one field. +- Bound the fan-out - never `Promise.all(rows.map(fn))` when `rows` is a query result and `fn` touches the DB. Use `mapConcurrent(items, limit, fn)` (`@openora/core/server`). Lint: `oss-module-shape/no-unbounded-db-fanout`. +- Critical mutations are transactional and idempotent - a DB guard inside the transaction, not just an `idempotencyKey`. + +```ts +await db.transaction(async (t) => { + if (await ledgerExists(t, idempotencyKey)) return; + await insertLedger(t, { idempotencyKey, amount }); +}); +``` + +## Migrations + +- Never hand-edit generated migrations, `docs/openapi.json`, or `docs/catalog.json`. Change the `pgTable`, then run `pnpm regen`. +- Every module owns its own `drizzle/migrations/` + `__drizzle_migrations_` tracking table, co-located with its schema. One shared database, one journal per module. +- A Postgres extension an index needs (for example `pg_trgm`) goes in the module `migrate()` `extensions` option, never hand-edited into a regenerated migration. diff --git a/package.json b/package.json index 42df91a5..70617b0a 100644 --- a/package.json +++ b/package.json @@ -66,7 +66,7 @@ "rulesync": "15.1.0", "tsx": "4.23.1", "turbo": "2.10.7", - "typescript": "7.0.2" + "typescript": "6.0.3" }, "engines": { "node": ">=26.0.0", diff --git a/packages/core/package.json b/packages/core/package.json index 243624e0..68ebf727 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -590,7 +590,7 @@ "@types/react": "19.2.17", "@types/react-dom": "19.2.3", "drizzle-kit": "0.31.10", - "typescript": "7.0.2", + "typescript": "6.0.3", "vite-tsconfig-paths": "6.1.1", "vitest": "4.1.8" }, diff --git a/packages/core/src/admin-console/__tests__/backoffice.service.test.ts b/packages/core/src/admin-console/__tests__/backoffice.service.int.test.ts similarity index 100% rename from packages/core/src/admin-console/__tests__/backoffice.service.test.ts rename to packages/core/src/admin-console/__tests__/backoffice.service.int.test.ts diff --git a/packages/core/src/casino/gaming/__tests__/admin-reporting.test.ts b/packages/core/src/casino/gaming/__tests__/admin-reporting.int.test.ts similarity index 100% rename from packages/core/src/casino/gaming/__tests__/admin-reporting.test.ts rename to packages/core/src/casino/gaming/__tests__/admin-reporting.int.test.ts diff --git a/packages/core/src/iam/__tests__/admin-player-activity.test.ts b/packages/core/src/iam/__tests__/admin-player-activity.int.test.ts similarity index 100% rename from packages/core/src/iam/__tests__/admin-player-activity.test.ts rename to packages/core/src/iam/__tests__/admin-player-activity.int.test.ts diff --git a/packages/create-openora/package.json b/packages/create-openora/package.json index bdccc316..5dacad60 100644 --- a/packages/create-openora/package.json +++ b/packages/create-openora/package.json @@ -36,7 +36,7 @@ "devDependencies": { "@openora/core": "workspace:*", "@types/node": "25.9.2", - "typescript": "7.0.2" + "typescript": "6.0.3" }, "engines": { "node": ">=26" diff --git a/packages/mcp/package.json b/packages/mcp/package.json index 6ca5b5a3..8650b8fd 100644 --- a/packages/mcp/package.json +++ b/packages/mcp/package.json @@ -38,6 +38,6 @@ }, "devDependencies": { "@types/node": "26.1.1", - "typescript": "7.0.2" + "typescript": "6.0.3" } } diff --git a/packages/testing/package.json b/packages/testing/package.json index 5cd03dbe..932e899c 100644 --- a/packages/testing/package.json +++ b/packages/testing/package.json @@ -29,7 +29,7 @@ "devDependencies": { "@types/node": "25.9.2", "@types/pg": "8.20.0", - "typescript": "7.0.2", + "typescript": "6.0.3", "vitest": "4.1.8" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef8f2dad..b03de238 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -20,7 +20,7 @@ importers: version: 2.31.1(@types/node@26.1.1) '@commitlint/cli': specifier: 21.2.1 - version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2) + version: 21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@6.0.3) '@commitlint/config-conventional': specifier: 21.2.0 version: 21.2.0 @@ -59,7 +59,7 @@ importers: version: 8.22.0 rulesync: specifier: 15.1.0 - version: 15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@7.0.2)) + version: 15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@6.0.3)) tsx: specifier: 4.23.1 version: 4.23.1 @@ -67,8 +67,8 @@ importers: specifier: 2.10.7 version: 2.10.7 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 apps/mcp-server-dev: dependencies: @@ -92,8 +92,8 @@ importers: specifier: 8.20.0 version: 8.20.0 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 packages/core: dependencies: @@ -180,11 +180,11 @@ importers: specifier: 0.31.10 version: 0.31.10 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 vite-tsconfig-paths: specifier: 6.1.1 - version: 6.1.1(typescript@7.0.2)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) + version: 6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) vitest: specifier: 4.1.8 version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) @@ -198,8 +198,8 @@ importers: specifier: 25.9.2 version: 25.9.2 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 packages/mcp: dependencies: @@ -217,8 +217,8 @@ importers: specifier: 26.1.1 version: 26.1.1 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 packages/testing: dependencies: @@ -245,8 +245,8 @@ importers: specifier: 8.20.0 version: 8.20.0 typescript: - specifier: 7.0.2 - version: 7.0.2 + specifier: 6.0.3 + version: 6.0.3 vitest: specifier: 4.1.8 version: 4.1.8(@types/node@25.9.2)(@vitest/coverage-v8@4.1.8)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)) @@ -1775,126 +1775,6 @@ packages: '@types/react@19.2.17': resolution: {integrity: sha512-MXfmqaVPEVgkBT/aY0aGCkRWWtByiYQXo3xdQ8r5RzuFrPiRn8Gar2tQdXSUQ2GKV3bkXckek89V8wQBY2Q/Aw==} - '@typescript/typescript-aix-ppc64@7.0.2': - resolution: {integrity: sha512-MTKKkWB7p/0E9xi1d1tHtZ5PiLkGEMIq88pK2CubZjOsLtYTLqhgIgi6zepFa+9GHZ6h05NMCkQxGKiPXMxXtQ==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [aix] - - '@typescript/typescript-darwin-arm64@7.0.2': - resolution: {integrity: sha512-gowzar9MwS/aRWp6f3a4KUqzRjAZjOsmGNCM6LcTgXum+dBfgsBVMN+AgvOCCbguXyick6LJhpBszxMebJ8syA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [darwin] - - '@typescript/typescript-darwin-x64@7.0.2': - resolution: {integrity: sha512-SZ9xZInqApNlNGc9s0W1VSsktYSOe9cFqNOIqmN1Gs8SmkjKZYFt017G4VwPxASInODuAdbTW7sXiFUf893RgA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [darwin] - - '@typescript/typescript-freebsd-arm64@7.0.2': - resolution: {integrity: sha512-W5NH4y/J0plIIS5b2xvTEkU7JFxyqdMAOgf+Ilhl0vHQXKO5dZoxd+C/jEtq56c4F3wk71RB4BMRQ2XdI+bwYQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [freebsd] - - '@typescript/typescript-freebsd-x64@7.0.2': - resolution: {integrity: sha512-UMGDx5sTpzNw3WiPebH7l90IWfJggEd+egHt/q6p7/Cm3zqoV7VxkGXt+3DxPIw8CcmvAB0j3sVVfbhX+M4Tpw==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [freebsd] - - '@typescript/typescript-linux-arm64@7.0.2': - resolution: {integrity: sha512-Qh4eU4/y3yDjnfjjyPYihMj5/ODIlmt+Bzu17OI+fiSRDW57QmU5SiN63exPRNJPKUzcc1INa1NXdrJ+MqHjUQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [linux] - - '@typescript/typescript-linux-arm@7.0.2': - resolution: {integrity: sha512-gffT3xPz9sR7j/YJExkyPntrI0P2EP9XbOyWzth2/Gs0RstK+90RBcO0ncXoXy/beYll1SXw846Nf2zdnEz0QQ==} - engines: {node: '>=16.20.0'} - cpu: [arm] - os: [linux] - - '@typescript/typescript-linux-loong64@7.0.2': - resolution: {integrity: sha512-uEHck9i8hoAzXPiYRib1O7miOnz23SxIeVl6F4LXox+qov1K35jHcEW6VHKvZI+pyvl7fZEP4MCU5LYvIq1GuQ==} - engines: {node: '>=16.20.0'} - cpu: [loong64] - os: [linux] - - '@typescript/typescript-linux-mips64el@7.0.2': - resolution: {integrity: sha512-R4KvAMnE43W5Qeqb0Ly56O3mWMWIAgsMyz36DCaycd5nbg/9kzm0liw3JocfRqyJY0KPmzFjbswozXyW0DnIYA==} - engines: {node: '>=16.20.0'} - cpu: [mips64el] - os: [linux] - - '@typescript/typescript-linux-ppc64@7.0.2': - resolution: {integrity: sha512-DORx5b3sd/4S7eayxm4FQv+A7CrkUIGRaHiwI8oiHTAI1fAPWhF4J0vAlkC8biAlHSVVwxMQ3tjZ2/DVbnQiiA==} - engines: {node: '>=16.20.0'} - cpu: [ppc64] - os: [linux] - - '@typescript/typescript-linux-riscv64@7.0.2': - resolution: {integrity: sha512-wf0jqEDOjrPRnKwYRyyJDRo11KMbvMFrU+q4zqKyChODBzvlkbhNQfKvLxQCcwTpdDaXSHZTVuh0JoCrKCUMHQ==} - engines: {node: '>=16.20.0'} - cpu: [riscv64] - os: [linux] - - '@typescript/typescript-linux-s390x@7.0.2': - resolution: {integrity: sha512-IkwJc3L7yhytWd/ewjyxNDfOmswCm9GWMJT/ue/dU4aZNbwZeYAetq42VyLmsmSjvoX7z74X6ZaYCtzAr0EuGw==} - engines: {node: '>=16.20.0'} - cpu: [s390x] - os: [linux] - - '@typescript/typescript-linux-x64@7.0.2': - resolution: {integrity: sha512-EYdf2cNg7rgCWJnxCdJ+F3V39O8ihb37eHAu1LK8oAFizgTQbPOK7zHHXbPt8rX24COqODXeI3sIf0fCXG7H/A==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [linux] - - '@typescript/typescript-netbsd-arm64@7.0.2': - resolution: {integrity: sha512-+polYF4MF04aPpO5FTkHran9yUQDSXqy5GiSDKpsll5jy3l3+g9QLhpf39T+ePtefhXLOGrLl0QIjkQP6VnelA==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [netbsd] - - '@typescript/typescript-netbsd-x64@7.0.2': - resolution: {integrity: sha512-8YIT0EHM/3dq10ZOVF/A7pc/YSMtbcecct4rWtexrnSCHOPcpC2KTLXfTCR6vDpnSiY12heNb1GiN/wu+T/FyA==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [netbsd] - - '@typescript/typescript-openbsd-arm64@7.0.2': - resolution: {integrity: sha512-APT8+ClYnuYm1u9+kgGXoMj2VzWzcymwh2gNSQVySHfkRDGOTVkoWLjCmOQSaO+PoqQ57B0flRp9SA+7GnnkzQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [openbsd] - - '@typescript/typescript-openbsd-x64@7.0.2': - resolution: {integrity: sha512-yX7s+Q0Dln0Dt9tEzZsAjXXR/+ytBM7AlglaqyeMPxQszJ1JhlJdZ6jLA+IzldHtflX81em7lDao1xXu+aRRkg==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [openbsd] - - '@typescript/typescript-sunos-x64@7.0.2': - resolution: {integrity: sha512-dLJDGaLZ1D4HPQn62u1n8mBDkJREwMsAkCdkwd4Ieqw+x3TUyTsqY0YiBCtE6H6OzzgGk3iuZ3vFWRS+E8/d1g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [sunos] - - '@typescript/typescript-win32-arm64@7.0.2': - resolution: {integrity: sha512-Gyl1Vy6OsWesLzmq+EP0Fb7b4Nid5232AvcA2SFcdYreldpNtYFFofPjnt62y9hQy7VTaZp65ICJjuAQRaVcIQ==} - engines: {node: '>=16.20.0'} - cpu: [arm64] - os: [win32] - - '@typescript/typescript-win32-x64@7.0.2': - resolution: {integrity: sha512-0BQ3HkAHHlKLSp1qRvf3SUhGpGsDuhB/jgFw75guyqbxJqEaS0Cw/VFO8i2nHglJUzQCRtMMR/IBAKE3ETMC4g==} - engines: {node: '>=16.20.0'} - cpu: [x64] - os: [win32] - '@valibot/to-json-schema@1.7.1': resolution: {integrity: sha512-3qkmU6KXWh8GIThEAW3kuRHPQBMjWkKy+Ppz3WkUucx53DTpOa6siMn4xDGSOhlVyMrDaJTCTMLYPZVAIk1P0A==} peerDependencies: @@ -3887,9 +3767,9 @@ packages: resolution: {integrity: sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==} engines: {node: '>= 18'} - typescript@7.0.2: - resolution: {integrity: sha512-8FYau96o3NKOhbjKi/qNvG/W5jhzxkbdm5sj9AbZ/5T5sWqn3hJgLfGx27sRKZWTvyzCP8dLRBTf5tBTSRVUNA==} - engines: {node: '>=16.20.0'} + typescript@6.0.3: + resolution: {integrity: sha512-y2TvuxSZPDyQakkFRPZHKFm+KKVqIisdg9/CZwm9ftvKXLP8NRWj38/ODjNbr43SsoXqNuAisEf1GdCxqWcdBw==} + engines: {node: '>=14.17'} hasBin: true uint8array-extras@1.5.0: @@ -4357,12 +4237,12 @@ snapshots: '@colors/colors@1.5.0': optional: true - '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@7.0.2)': + '@commitlint/cli@21.2.1(@types/node@26.1.1)(conventional-commits-parser@7.1.0)(typescript@6.0.3)': dependencies: '@commitlint/config-conventional': 21.2.0 '@commitlint/format': 21.2.0 '@commitlint/lint': 21.2.0 - '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@7.0.2) + '@commitlint/load': 21.2.0(@types/node@26.1.1)(typescript@6.0.3) '@commitlint/read': 21.2.1(conventional-commits-parser@7.1.0) '@commitlint/types': 21.2.0 tinyexec: 1.2.4 @@ -4407,14 +4287,14 @@ snapshots: '@commitlint/rules': 21.2.0 '@commitlint/types': 21.2.0 - '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@7.0.2)': + '@commitlint/load@21.2.0(@types/node@26.1.1)(typescript@6.0.3)': dependencies: '@commitlint/config-validator': 21.2.0 '@commitlint/execute-rule': 21.0.1 '@commitlint/resolve-extends': 21.2.0 '@commitlint/types': 21.2.0 - cosmiconfig: 9.0.2(typescript@7.0.2) - cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2) + cosmiconfig: 9.0.2(typescript@6.0.3) + cosmiconfig-typescript-loader: 6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3) es-toolkit: 1.48.1 is-plain-obj: 4.1.0 picocolors: 1.1.1 @@ -5613,69 +5493,9 @@ snapshots: dependencies: csstype: 3.2.3 - '@typescript/typescript-aix-ppc64@7.0.2': - optional: true - - '@typescript/typescript-darwin-arm64@7.0.2': - optional: true - - '@typescript/typescript-darwin-x64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-freebsd-x64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm64@7.0.2': - optional: true - - '@typescript/typescript-linux-arm@7.0.2': - optional: true - - '@typescript/typescript-linux-loong64@7.0.2': - optional: true - - '@typescript/typescript-linux-mips64el@7.0.2': - optional: true - - '@typescript/typescript-linux-ppc64@7.0.2': - optional: true - - '@typescript/typescript-linux-riscv64@7.0.2': - optional: true - - '@typescript/typescript-linux-s390x@7.0.2': - optional: true - - '@typescript/typescript-linux-x64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-netbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-arm64@7.0.2': - optional: true - - '@typescript/typescript-openbsd-x64@7.0.2': - optional: true - - '@typescript/typescript-sunos-x64@7.0.2': - optional: true - - '@typescript/typescript-win32-arm64@7.0.2': - optional: true - - '@typescript/typescript-win32-x64@7.0.2': - optional: true - - '@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2))': + '@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3))': dependencies: - valibot: 1.4.1(typescript@7.0.2) + valibot: 1.4.1(typescript@6.0.3) '@vitest/coverage-v8@4.1.8(vitest@4.1.8)': dependencies: @@ -6011,21 +5831,21 @@ snapshots: object-assign: 4.1.1 vary: 1.1.2 - cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@7.0.2))(typescript@7.0.2): + cosmiconfig-typescript-loader@6.3.0(@types/node@26.1.1)(cosmiconfig@9.0.2(typescript@6.0.3))(typescript@6.0.3): dependencies: '@types/node': 26.1.1 - cosmiconfig: 9.0.2(typescript@7.0.2) + cosmiconfig: 9.0.2(typescript@6.0.3) jiti: 2.6.1 - typescript: 7.0.2 + typescript: 6.0.3 - cosmiconfig@9.0.2(typescript@7.0.2): + cosmiconfig@9.0.2(typescript@6.0.3): dependencies: env-paths: 2.2.1 import-fresh: 3.3.1 js-yaml: 4.2.0 parse-json: 5.2.0 optionalDependencies: - typescript: 7.0.2 + typescript: 6.0.3 cron-parser@4.9.0: dependencies: @@ -6301,7 +6121,7 @@ snapshots: fast-uri@3.1.2: {} - fastmcp@4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4): + fastmcp@4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@standard-schema/spec': 1.1.0 @@ -6313,7 +6133,7 @@ snapshots: strict-event-emitter-types: 2.0.0 undici: 7.28.0 uri-templates: 0.2.0 - xsschema: 0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) + xsschema: 0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3) yargs: 18.0.0 zod: 4.4.3 zod-to-json-schema: 3.25.2(zod@4.4.3) @@ -7281,17 +7101,17 @@ snapshots: transitivePeerDependencies: - supports-color - rulesync@15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@7.0.2)): + rulesync@15.1.0(jose@6.2.3)(valibot@1.4.1(typescript@6.0.3)): dependencies: '@modelcontextprotocol/sdk': 1.29.0(zod@4.4.3) '@octokit/request-error': 7.1.0 '@octokit/rest': 22.0.1 '@toon-format/toon': 4.0.0 - '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@7.0.2)) + '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@6.0.3)) commander: 15.0.0 effect: 3.22.0 es-toolkit: 1.49.0 - fastmcp: 4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4) + fastmcp: 4.8.0(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(jose@6.2.3)(sury@10.0.4) globby: 16.2.2 gray-matter: 4.0.3 js-yaml: 5.2.1 @@ -7557,9 +7377,9 @@ snapshots: '@ts-morph/common': 0.29.0 code-block-writer: 13.0.3 - tsconfck@3.1.6(typescript@7.0.2): + tsconfck@3.1.6(typescript@6.0.3): optionalDependencies: - typescript: 7.0.2 + typescript: 6.0.3 tsconfig-paths-webpack-plugin@4.2.0: dependencies: @@ -7603,28 +7423,7 @@ snapshots: media-typer: 1.1.0 mime-types: 3.0.2 - typescript@7.0.2: - optionalDependencies: - '@typescript/typescript-aix-ppc64': 7.0.2 - '@typescript/typescript-darwin-arm64': 7.0.2 - '@typescript/typescript-darwin-x64': 7.0.2 - '@typescript/typescript-freebsd-arm64': 7.0.2 - '@typescript/typescript-freebsd-x64': 7.0.2 - '@typescript/typescript-linux-arm': 7.0.2 - '@typescript/typescript-linux-arm64': 7.0.2 - '@typescript/typescript-linux-loong64': 7.0.2 - '@typescript/typescript-linux-mips64el': 7.0.2 - '@typescript/typescript-linux-ppc64': 7.0.2 - '@typescript/typescript-linux-riscv64': 7.0.2 - '@typescript/typescript-linux-s390x': 7.0.2 - '@typescript/typescript-linux-x64': 7.0.2 - '@typescript/typescript-netbsd-arm64': 7.0.2 - '@typescript/typescript-netbsd-x64': 7.0.2 - '@typescript/typescript-openbsd-arm64': 7.0.2 - '@typescript/typescript-openbsd-x64': 7.0.2 - '@typescript/typescript-sunos-x64': 7.0.2 - '@typescript/typescript-win32-arm64': 7.0.2 - '@typescript/typescript-win32-x64': 7.0.2 + typescript@6.0.3: {} uint8array-extras@1.5.0: {} @@ -7646,17 +7445,17 @@ snapshots: uri-templates@0.2.0: {} - valibot@1.4.1(typescript@7.0.2): + valibot@1.4.1(typescript@6.0.3): optionalDependencies: - typescript: 7.0.2 + typescript: 6.0.3 vary@1.1.2: {} - vite-tsconfig-paths@6.1.1(typescript@7.0.2)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)): + vite-tsconfig-paths@6.1.1(typescript@6.0.3)(vite@8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1)): dependencies: debug: 4.4.3 globrex: 0.1.2 - tsconfck: 3.1.6(typescript@7.0.2) + tsconfck: 3.1.6(typescript@6.0.3) vite: 8.0.16(@types/node@25.9.2)(esbuild@0.28.1)(jiti@2.7.0)(tsx@4.23.1) transitivePeerDependencies: - supports-color @@ -7734,9 +7533,9 @@ snapshots: ws@8.21.1: optional: true - xsschema@0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@7.0.2)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3): + xsschema@0.4.4(@valibot/to-json-schema@1.7.1(valibot@1.4.1(typescript@6.0.3)))(effect@3.22.0)(sury@10.0.4)(zod-to-json-schema@3.25.2(zod@4.4.3))(zod@4.4.3): optionalDependencies: - '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@7.0.2)) + '@valibot/to-json-schema': 1.7.1(valibot@1.4.1(typescript@6.0.3)) effect: 3.22.0 sury: 10.0.4 zod: 4.4.3 From 58f37bce262bc85cf2f96944b6922312ad0caaf5 Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Fri, 31 Jul 2026 14:08:07 +0200 Subject: [PATCH 16/17] chore(rules): consolidate conventions guidance --- .rulesync/commands/scaffold-module.md | 2 +- .rulesync/rules/conventions.md | 15 ++- .rulesync/rules/db-conventions.md | 24 ----- .rulesync/rules/overview.md | 10 +- .rulesync/skills/add-feature/SKILL.md | 90 +--------------- .rulesync/skills/create-pr/SKILL.md | 44 +------- .rulesync/skills/enhance-prompt/SKILL.md | 48 +-------- .rulesync/skills/oss-review/SKILL.md | 108 +------------------- .rulesync/skills/review/SKILL.md | 103 +------------------ .rulesync/subagents/contract-reviewer.md | 4 +- .rulesync/subagents/dev.md | 2 +- .rulesync/subagents/module-author.md | 4 +- .rulesync/subagents/quality-reviewer.md | 4 +- docs/standards/skills/delivery.md | 54 ++++++++++ docs/standards/skills/prompt-enhancement.md | 40 ++++++++ docs/standards/skills/review.md | 79 ++++++++++++++ 16 files changed, 211 insertions(+), 420 deletions(-) delete mode 100644 .rulesync/rules/db-conventions.md create mode 100644 docs/standards/skills/delivery.md create mode 100644 docs/standards/skills/prompt-enhancement.md create mode 100644 docs/standards/skills/review.md diff --git a/.rulesync/commands/scaffold-module.md b/.rulesync/commands/scaffold-module.md index 3757b00a..ae75c906 100644 --- a/.rulesync/commands/scaffold-module.md +++ b/.rulesync/commands/scaffold-module.md @@ -9,7 +9,7 @@ Run `pnpm gen module $ARGUMENTS` (args: ` `, eg `casino tournament The scaffold ships a buildable module - a `list` route wired end to end (contract -> router -> service) over a sample table - so `pnpm regen && pnpm verify` is green immediately. The generator marks edit regions with `// AGENT: implement here` - fill those, leave the wiring alone: 1. `plugin.ts` - verify the module id + any default adapter bindings (`ctx.provide(TOKEN, () => new DefaultImpl())`). -2. `schema/index.ts` - Drizzle tables (`propose-table-change` via MCP first; see `db-conventions`). +2. `schema/index.ts` - Drizzle tables (`propose-table-change` via MCP first; see `docs/standards/database.md`). 3. `contract/index.ts` - Zod input/output schemas for the routes (the single source of wire truth). 4. `service/.service.ts` - business logic; inject `DRIZZLE` + `EVENT_BUS` + adapter ports via the constructor; never inline fetch/SQL. Audit every mutation. 5. `router/index.ts` - oRPC routes with imported schemas; admin routes call `await adminGuard.assert(context)` first. diff --git a/.rulesync/rules/conventions.md b/.rulesync/rules/conventions.md index e1a0db46..08d42f54 100644 --- a/.rulesync/rules/conventions.md +++ b/.rulesync/rules/conventions.md @@ -6,12 +6,23 @@ globs: - '**/*.ts' - '**/*.tsx' - '**/*.mjs' -description: Engineering code conventions (TS, headless backend) - apply to every code change. + - 'packages/**/schema/**' + - 'packages/**/service/**' + - 'packages/**/adapters/**' + - 'packages/core/src/**/admin-*.ts' + - 'packages/**/seed/**' + - 'packages/**/drizzle/**' + - 'packages/**/drizzle.config.ts' + - 'packages/**/migrate.ts' + - 'packages/core/src/server/db/**' + - 'packages/testing/src/**' + - 'tools/db/**' +description: Engineering conventions - code, SQL, Drizzle, migrations, and database tooling. --- # Engineering Conventions -The always-on core of the code standard: what you must obey while typing. Detail, examples and rationale live in `docs/standards/` - read the one file that matches the change instead of carrying all of it. Async seams: `messaging-and-microservices`. SQL / Drizzle: `db-conventions`, then `docs/standards/database.md`. Repo map, decision tree, dependency rules: `overview`. +The always-on core of the code standard: what you must obey while typing. Detail, examples and rationale live in `docs/standards/` - read the one file that matches the change instead of carrying all of it. Async seams: `messaging-and-microservices`. SQL / Drizzle: `docs/standards/database.md`. Repo map, decision tree, dependency rules: `overview`. | Change you are making | Read first | | -------------------------------------- | ------------------------------------ | diff --git a/.rulesync/rules/db-conventions.md b/.rulesync/rules/db-conventions.md deleted file mode 100644 index 509cbb5e..00000000 --- a/.rulesync/rules/db-conventions.md +++ /dev/null @@ -1,24 +0,0 @@ ---- -root: false -targets: - - '*' -globs: - - 'packages/**/schema/**' - - 'packages/**/service/**' - - 'packages/**/adapters/**' - - 'packages/core/src/**/admin-*.ts' - - 'packages/**/seed/**' - - 'packages/**/drizzle/**' - - 'packages/**/drizzle.config.ts' - - 'packages/**/migrate.ts' - - 'packages/core/src/server/db/**' - - 'packages/testing/src/**' - - 'tools/db/**' -description: SQL / Drizzle routing rule - read the database standard before editing matching files. ---- - -# Database conventions - -For every matching file, read [`docs/standards/database.md`](../../docs/standards/database.md) in full before editing. - -That file is the sole authoritative database and Drizzle standard. Do not duplicate its rules here. diff --git a/.rulesync/rules/overview.md b/.rulesync/rules/overview.md index 87465790..d9f09275 100644 --- a/.rulesync/rules/overview.md +++ b/.rulesync/rules/overview.md @@ -10,7 +10,7 @@ globs: Canonical brief for AI agents and humans. Per-tool files (`AGENTS.md`, `CLAUDE.md`, `.github/copilot-instructions.md`, `.codex/config.toml`, subagent + command mirrors) are generated by [rulesync](https://github.com/dyoshikawa/rulesync) from `.rulesync/`. Edit the source, run `pnpm gen:agents`. Never hand-edit generated files. -Sibling rules (load on demand; do not reopen settled questions): `conventions` (the always-on code standard, with a table routing to the deep-dive file in `docs/standards/`), `messaging-and-microservices` (async seams, command vs event vs job, outbox), `db-conventions` (routes matching SQL / Drizzle files to `docs/standards/database.md`). Module layering, DI, ports and the shared-helper table live in `docs/standards/module-structure.md`. +Sibling rules (load on demand; do not reopen settled questions): `conventions` (the always-on code and database standard, with a table routing to the matching deep-dive file in `docs/standards/`), `messaging-and-microservices` (async seams, command vs event vs job, outbox). Module layering, DI, ports and the shared-helper table live in `docs/standards/module-structure.md`. ## Mission @@ -61,9 +61,9 @@ extensions.config.ts # the single registry of enabled plugins - **Extend/override an existing module** -> overlay plugin: `pnpm gen plugin ` -> `extensions//plugin.ts` (repo root here; the consumer's app when deployed). - **New HTTP route** -> the module's `router/index.ts` via `pnpm gen route `. Player routes resolve the caller from `x-user-id`; admin routes MUST be guarded (next). - **Admin-only route** -> `plugin.ts` resolves `AdminGuard` (`c.get(ADMIN_GUARD)`, seeded by `createApp`) and passes it into the router; `await adminGuard.assert(context)` is the handler's FIRST line. The single admin-enforcement point - never re-implement the role check. -- **New DB table** -> a Drizzle `pgTable` in the module's `schema/index.ts`; run `propose-table-change` (MCP) first, then `pnpm regen`. See `db-conventions`. +- **New DB table** -> a Drizzle `pgTable` in the module's `schema/index.ts`; run `propose-table-change` (MCP) first, then `pnpm regen`. See `docs/standards/database.md`. - **Reusable Zod schema** -> `packages/core/src/contracts/schemas/.ts`. Module-local schemas in the module's `contract/`. -- **Enum / status value set** -> a values + schema + type triple on the contract surface (cross-domain: core `contracts/schemas/`; domain-local: the module's `contract/`), pgEnum derived from the tuple. `docs/standards/types.md` + `db-conventions` > Enums. +- **Enum / status value set** -> a values + schema + type triple on the contract surface (cross-domain: core `contracts/schemas/`; domain-local: the module's `contract/`), pgEnum derived from the tuple. `docs/standards/types.md` + `docs/standards/database.md` > Enums. - **Cross-module event** -> declare the payload in `domainEventSchemas` (`packages/core/src/contracts/schemas/events.ts`), emit via `EventBus`, subscribe with `ctx.events.on(...)`. ADR-0010; detail in `messaging-and-microservices`. - **Frontend UI** -> NOT here (headless). Consumer builds it over HTTP via `@openora/core/react`. - **New data hook** -> `packages/core/src/react/hooks/` (domain-specific: that domain's `react/` dir). Hand-write `useMemo`/`useCallback` for stability-contract returns (consumer's React Compiler skips `node_modules`) - `docs/standards/react-sdk.md`. @@ -78,7 +78,7 @@ Cross-cutting basics (kebab files, PascalCase types, `Schema` + inferred ` - Packages: `@openora/`. Public API = package/subpath entry + read-only `/schema`; internals are a lint error. - oRPC routers namespaced by module (`wallet.transactions.list`). -- SQL / Drizzle identifiers: `db-conventions`. +- SQL / Drizzle identifiers: `docs/standards/database.md`. ## Dependency rules (two-layer enforcement, both in `pnpm verify`) @@ -95,7 +95,7 @@ Lint-enforced cross-cutting bans in `conventions`: `any` outside tests, `interfa - Ad-hoc/duplicated Zod schemas in routers/services - schemas live in the module's `contract/` or core contracts; derive with `.pick/.omit/.partial/.extend/.merge`, don't re-type fields. - Re-exporting types "to be nice" - import from where defined. -- SQL anti-patterns (bare `timestamp()`, CamelCase identifiers, hand-edited migrations) - `db-conventions`. +- SQL anti-patterns (bare `timestamp()`, CamelCase identifiers, hand-edited migrations) - `docs/standards/database.md`. ## Run locally diff --git a/.rulesync/skills/add-feature/SKILL.md b/.rulesync/skills/add-feature/SKILL.md index 99aadadf..ddb4d3eb 100644 --- a/.rulesync/skills/add-feature/SKILL.md +++ b/.rulesync/skills/add-feature/SKILL.md @@ -1,92 +1,8 @@ --- name: add-feature -description: > - Deliver an OSS platform-core feature end-to-end: context collection -> plan + approval -> - implementation -> unit/integration tests -> e2e test cases + run -> fixes -> create-pr -> - Jira transition (only on explicit ask) -> Slack notice. Fed by a work-order (from the consumer - add-feature handoff) or an OSS ticket. Use on "add feature", "plan ", "deliver this - OSS change", or /add-feature [path|ticket]. Read-only until the plan is approved; never pushes - without explicit OK. +description: Deliver an OSS platform-core feature from scoped context through verified PR delivery. Use on "add feature", "plan ", "deliver this OSS change", or /add-feature [path|ticket]. --- -# add-feature (oss) +# add-feature -Platform-core twin of the consumer `add-feature` skill. Use in this repo when a consumer feature needs `@openora/*` core changes (via a work-order) or for a standalone core feature. Owns the whole development + delivery cycle. - -``` -1 context (+ GRILL) -> 2 plan + approval -> 3 implement -> 4 unit/integration tests --> 5 review -> 6 e2e cases + run -> 7 fix loop -> 8 regen/verify -> 9 create-pr --> 10 reviewers -> 11 Jira (ONLY if asked) -> 12 Slack draft -``` - -## Coordinates - -- Repo: `github.com/blurifycom/openora`. PR target: `dev` (chain `dev -> stage` + tags). Tool: `gh` CLI. -- Codebase inspection: `oss-dev` MCP (read-only). Headless backend - contracts + Hono + oRPC + Drizzle + react SDK + plugins. No UI here. -- Issue tracker / chat: use whatever MCP integrations the session has (Jira/Confluence, Slack). Chat notices are **draft only**, to the team's agreed channel. No integration connected: skip those steps silently. - -## The contract - -- **Read-only until the plan is approved.** No edits/commits/pushes/Jira writes before sign-off. -- **Never write to Jira unless the user explicitly asks for that specific write.** Approving the plan or opening the PR is NOT Jira authorization. Reading is always fine. -- Reuse `create-pr`, `/regen`, `/pre-pr`, `/verify`. Delegate to the roster subagents - don't hand-write their work. - -## Steps - -### 1. Resolve input + collect context (read-only) - -`$ARGUMENTS` is a work-order path (eg `~/.claude/plans/-oss.md`) or a ticket. Read it (goal, consumer feature it unblocks, core surface, contract/schema impact, acceptance), then gather in parallel: the ticket (tracker MCP if connected), `docs/` ADRs + `catalog.json` + `openapi.json`, the rule docs (`conventions`, `messaging-and-microservices`, `db-conventions`) plus the relevant `docs/standards/` files, the codebase (`oss-dev` MCP + Explore), chat/wiki for prior design discussion (search the ticket key). Run the `enhance-prompt` pre-step on the ask first, so this gather stays scoped and 1a grills against a clear brief. - -#### 1a. Grill the user before planning (MANDATORY) - -Tickets under-specify. Before writing the plan, surface every decision that changes the build via `AskUserQuestion` (batch up to 4, multiple rounds) - never assume a default silently. Each option leads with your recommendation + trade-off. Cover at minimum: scope edges (in vs out), domain + seam placement (which module owns it; boundary risk), data-model forks (enum vs additive, history vs current, nullable/defaults), regulatory angle (jurisdiction, gating, idempotency, audit actor/resource), reuse vs build (existing helper/port/route), in-flight collisions, config surface (`platform-config` vs code). Fold answers into the plan's "Locked decisions". - -### 2. Plan + approval (the gate) - -Present: goal, AC, locked decisions with sources, exact core surface (packages, contracts, tables, events, adapter tokens), boundary/breaking-change impact, risks, task breakdown mapped to subagents. **Require explicit approval before editing.** - -### 3. Implement - -Pick the implementer per slice: `expert` (fuzzy -> requirements, advisory), `module-author` (new module), `plugin-author` (overlay), `dev` (cross-module logic, contracts, services, SDK). - -### 4. Unit + integration tests - -The implementer ships co-located Vitest tests with the slice (`createTestDb` against real Postgres for services; `bootTestApp` for integration) - part of the deliverable, not an afterthought. - -### 5. E2E - -Derive the checklist from the AC first: happy path, edge cases, authz negatives (401/403), money/idempotency, audit entry per mutation. Then `qa` executes it API-level. E2e failures go back to the step-3 implementer BEFORE any review - don't review code that doesn't work. - -### 6. Review (findings only) - -In parallel: `contract-reviewer` (boundaries, contract/schema drift, breaking changes) + `security-reviewer` (money, authz, PII) + `quality-reviewer` (performance, duplication, simplification, conventions). Pass each what the e2e run proved so reviewers dig where tests can't reach. Loop findings back to the implementer; re-review if the surface changed, and re-run the affected e2e checks if a fix changed behavior. - -### 7. Fix loop - -Every review/e2e finding goes back through the step-3 implementer until green; re-run the affected check after each fix. - -### 8. Regen + verify - -`/regen` if contracts/Drizzle changed; `docs` agent only if prose docs / agent surface changed; `/pre-pr` (`pnpm verify` + `pnpm check:drift`). Don't proceed on red or a catalog diff. - -### 9. Open the PR - -Invoke `create-pr` (target `dev`). It commits conventional, reports the SHA, **asks for "yes push"**, then pushes and opens the PR. Never bypass the push-consent gate. - -### 10. Reviewers from CODEOWNERS - -Match changed paths against `CODEOWNERS`, then `gh pr edit --add-reviewer `. PR description = plan summary + AC + bare ticket key (`ABC-45`, never a URL). No internal links/hostnames/secrets/PII. - -### 11. Jira - ONLY on explicit user request - -No transition, comment, worklog, or link by default. When the user asks in their own words (eg "move ABC-210 to Code Review"): fetch valid transitions via the tracker MCP, apply the matching one. Never post PR-link/status comments on the ticket. - -### 12. Slack notice - -Draft (never direct-send) a **single line** to the team's agreed notice channel: emoji + PR/task name as a link, eg `馃憠 RBAC backend - PR #11`. - -## Rules - -- Grill before you plan; read-only until approval; never push without a per-action "yes push". -- `/pre-pr` green before push; `/regen` after any contract/schema change. -- One PR = one concern; respect boundaries (no cross-module imports). +Read [Delivery workflows - Add feature](../../../docs/standards/skills/delivery.md#add-feature) in full before acting. diff --git a/.rulesync/skills/create-pr/SKILL.md b/.rulesync/skills/create-pr/SKILL.md index 702732f5..d0eb182b 100644 --- a/.rulesync/skills/create-pr/SKILL.md +++ b/.rulesync/skills/create-pr/SKILL.md @@ -1,46 +1,8 @@ --- name: create-pr -description: Commit, push, and open a GitHub Pull Request following this repo's promotion chain (dev -> stage -> tag). Use on "create pr", "open a pr", "/create-pr", or "promote ". +description: Commit, push, and open a GitHub Pull Request following this repo's promotion chain. Use on "create pr", "open a pr", "/create-pr", or "promote ". --- -# create-pr (oss) +# create-pr -This repo is on **GitHub** (`github.com/blurifycom/openora`). Use the `gh` CLI. Branches are promoted along a fixed chain - never open a PR straight to `stage` from a feature branch. - -## Promotion chain - -| Source (current) branch | PR target | Notes | -| ----------------------- | --------- | -------------------------------------- | -| `dev` (default working) | `stage` | Promote accumulated work to staging | -| `stage` | (git tag) | Cut a release tag, no PR (see Release) | -| any `feat/*` / `fix/*` | `dev` | Feature work merges into dev first | - -Any other branch: target `dev`. - -## Steps - -1. **Determine source + target.** `git branch --show-current` -> the table above. -2. **Scope the commit.** `git status -s`. Commit ONLY changes belonging to this unit of work. Foreign/pre-existing edits in the tree: stage your files explicitly, tell the user what you left out. Never `git add -A` blindly. -3. **Commit.** Conventional-commit message with a valid scope (see `docs/standards/git-delivery.md`). No AI attribution trailers. Keep the message free of sensitive/internal data (below). -4. **Verify before pushing:** `pnpm verify`. Don't push a red tree. -5. **Push - STOP first.** Report the commit SHA, then ask for an explicit per-action "yes push". Invoking this skill is NOT push authorization; shared branches (`dev`, `stage`) especially. Only after the yes: `git push -u origin `. -6. **Open the PR** (after the confirmed push). Read `.github/pull_request_template.md` and fill it - it is the source of truth for the structure, so follow whatever sections it currently defines and don't invent your own. Answer the HTML comment in each section, then delete the comment. Drop a section it tells you to drop; tick a box only if it actually holds. - ``` - gh pr create --base --head --title "(): " --body-file - ``` - Reuse an existing open PR for the same head->base instead of duplicating (`gh pr list --head --base `). -7. **Report** the PR URL. - -## Release (the `-> tag` step) - -Production is a git tag cut from `stage`: `git tag vX.Y.Z && git push origin vX.Y.Z`. Only on an explicit user request; confirm the version first. - -## No sensitive data in titles / descriptions / commits - -They are the public-facing record. Never include: internal URLs (Jira/Confluence/Slack/Notion, dashboards, CI links - reference a ticket by bare key, eg `ABC-45`), secrets, tokens, customer/operator names, PII, internal IPs/hostnames/paths. When in doubt, leave it out. - -## Rules - -- NEVER push without an explicit per-action "yes push". Report SHA, ask, push only on yes. -- `pnpm verify` must pass before the push. -- One PR = one concern; split unrelated changes. +Read [Delivery workflows - Create pull request](../../../docs/standards/skills/delivery.md#create-pull-request) in full before acting. diff --git a/.rulesync/skills/enhance-prompt/SKILL.md b/.rulesync/skills/enhance-prompt/SKILL.md index 56628077..80bd01f9 100644 --- a/.rulesync/skills/enhance-prompt/SKILL.md +++ b/.rulesync/skills/enhance-prompt/SKILL.md @@ -1,53 +1,9 @@ --- name: enhance-prompt targets: ['*'] -description: > - Normalize any raw request into a grounded, structured brief before acting. The auto pre-step - for skills and subagents: extract the real intent, gather scoped context (issue tracker + - roadmap + docs/ADRs + catalog), surface only blocking ambiguities, and emit - objective / scope / constraints / deliverables / guardrails. Routes build-type asks into - `enhance-intent`. Skips asks that are already precise. Use on any fuzzy, broad, or multi-part - request, or at the start of a skill or agent that received a raw ask. +description: Normalize a raw request into a grounded, structured brief before acting. Use on fuzzy, broad, multi-part, or under-specified asks. --- # enhance-prompt -Turn a raw, rambling ask into a brief a stranger could execute - before any file is touched. A one-line ask is a starting point, not a spec; a wrong reading executed confidently wastes far more than the enhancement costs. - -Runs as a **pre-step**: skills and the orchestrator apply it to the incoming ask, then act on the enhanced brief - not the raw text. - -## Run it, or skip it - -- **Run** when the ask is fuzzy, broad, multi-part, or missing scope / target / success criteria. -- **Skip** - act directly - when the ask is already precise and self-contained: a specific edit, a single factual question, a mechanical change with an obvious target. Enhancing a clear one-liner just burns tokens. - -Match effort to the ask: a small task gets a one-line restatement, not a full brief. - -## Method - -1. **Restate the intent** in one line. If your restatement might be wrong, gather context or ask - don't guess. -2. **Classify**: build (feature / adapter / page / route) 路 review or audit 路 debug 路 refactor 路 docs 路 research 路 ops. For a **build** ask, hand off to the `enhance-intent` MCP tool (server `oss`) - it adds the platform catalog, requirements checklist, and the consumer `pnpm gen` playbook - and stop here. -3. **Gather scoped context** - targeted, never a data dump (token budget matters). Pull only what changes the plan: the issue tracker / roadmap, `docs/` + `docs/adr/`, the catalog MCP tools (`catalog-overview`, `list-adapters`, `describe-module`), and the code itself. Scope first, then fetch. -4. **Surface blocking ambiguities only** - the questions whose answers change what you build. Resolve the rest with stated defaults. Don't interrogate. -5. **Emit the brief**, sized to the task. - -## The brief - -- **Objective** - the outcome, in priority order. -- **Scope** - what's in; what's explicitly out. -- **Context** - the load-bearing facts found, with links (issue, doc, `file:line`). -- **Constraints** - conventions, module layering (`docs/standards/module-structure.md`), minimal-dependency + token budget, the public / open-source surface. -- **Deliverables** - what "done" produces. -- **Guardrails** - what not to touch; reversible-only; confirm-before-X. -- **Open questions** - only the blocking ones. - -## Modes - -- **Return for approval** (default): for non-trivial or ambiguous asks, present the brief, get a nod, then execute. Silently running a wrong reading multiplies the waste. -- **Enhance then proceed** (internal dispatch): the orchestrator enhances once at the top and passes the crisp brief to the subagent. Subagents act on the brief; they do not re-enhance their own input. - -## Rules - -- Intent-preserving: clarify the ask, never invent scope the user didn't want. -- Proportional: the brief is as short as the task allows. -- One pass at the top: enhance before context-gathering and before delegation, so the gather is targeted and the agents get a clean brief. +Read [Prompt enhancement](../../../docs/standards/skills/prompt-enhancement.md) in full before acting. diff --git a/.rulesync/skills/oss-review/SKILL.md b/.rulesync/skills/oss-review/SKILL.md index d7e8e3ed..04751b3f 100644 --- a/.rulesync/skills/oss-review/SKILL.md +++ b/.rulesync/skills/oss-review/SKILL.md @@ -1,112 +1,8 @@ --- name: oss-review -description: Multi-agent code review of the working branch against this repo's conventions, boundaries, contracts, security, messaging, and audit rules. Fans out a configurable number of parallel reviewers, each grounded in the rule docs, then synthesizes one verdict. Use on "review this", "oss review", "/oss-review", optionally "--agents N", "--base ", "--fix", a GitHub PR number, or paths. +description: Convention-grounded review of the working branch. Use on "review this", "oss review", "/oss-review", optionally "--agents N", "--base ", "--fix", a GitHub PR number, or paths. --- # oss-review -Orchestrate a parallel, convention-grounded code review of the current change set. You are the -orchestrator: scope the diff, fan out N reviewers across review dimensions, dedup their findings, -and report ONE verdict. Report-only by default - never edit unless `--fix` is passed. - -Copy this checklist and tick it off as you go: - -``` -oss-review: -- [ ] 1. Parse args (--agents / --base / PR# / paths / --fix) -- [ ] 2. Scope the diff; if empty, ask -- [ ] 3. Pick applicable dimensions (only those with touched files) -- [ ] 4. Spawn reviewers in ONE message (parallel) -- [ ] 5. Dedup + apply the evidence/confidence gate -- [ ] 6. Report one verdict (+ apply fixes only if --fix) -``` - -## 1. Parse `$ARGUMENTS` - -| Token | Meaning | Default | -| ------------------- | ------------------------------------------------------------- | ------------------------------- | -| `--agents N` | how many parallel reviewers to spawn (1-6) | one per applicable dimension | -| `--base ` | branch to diff against | `dev` (this repo's integration) | -| `` (e.g. 8) | a GitHub PR number - review that PR's diff (`gh pr diff `) | - | -| paths | restrict review to these files/dirs | whole diff | -| `--fix` | apply fixes for `[BLOCK]`/`[WARN]` findings after the review | off (report only) | - -## 2. Scope the diff (do this first) - -- Branch: `git diff ...HEAD --name-only` (default base `dev`). If empty, fall back to - unstaged/staged via `git status -s`; if still empty, ask what to review. -- PR number given: `gh pr diff ` for the patch + `gh pr view ` for intent. -- Group changed files by package/domain so reviewers and any file-split share the same map. - -## 3. Ground every reviewer (mandatory) - -Each spawned reviewer MUST read the actual changed code AND the rule docs that own its dimension -before judging - do not infer behavior from a diff hunk. If a finding depends on what a called -function does, open it; if you can't cite it, fetch it. These docs are the single source of truth - -cite them in findings: - -- `.claude/rules/conventions.md` - the portable code standard (philosophy, naming, types, functions, comments, testing, errors, deps, git). -- `docs/standards/module-structure.md` - module layering, DI, ports & adapters, shared helpers, FK rule. -- `.claude/rules/messaging-and-microservices.md` - command vs event vs job, outbox, service manifest. -- `CLAUDE.md` (AGENTS.md) - architecture pillars, dependency rules, forbidden patterns, the "Definition of done" audit requirement. -- The touched module's own `AGENTS.md` and any relevant `docs/adr/*.md`. - -## 4. Review dimensions - -Each dimension maps to a rule doc and, where one exists, a pre-scoped roster subagent. Spawn only the -dimensions whose files actually changed. - -| # | Dimension | Covers | Use subagent | -| --- | ---------------------- | ---------------------------------------------------------------------------------------- | ------------------------ | -| 1 | Boundaries & contracts | import-graph rules, oRPC `.input()/.output()`, Zod-first, schema/openapi drift, drizzle | `contract-reviewer` | -| 2 | Security & money | money idempotency/atomicity, authz/`AdminGuard`, secrets/PII, auth flows, injection | `security-reviewer` | -| 3 | Conventions & quality | naming, functional/immutable, no `any`/`interface`/default-export, comments WHY, tests | general (grounded in 搂3) | -| 4 | Messaging seams | command vs event vs job choice, outbox for must-not-lose events, idempotent handlers | general (grounded in 搂3) | -| 5 | Audit completeness | every state-changing action leaves a hash-chained `audit` entry (domain event or writer) | general (grounded in 搂3) | -| 6 | Operator/domain fit | does the change make igaming sense; launch-blockers (only if business logic changed) | `operator` | - -## 5. Allocate reviewers to `--agents N` - -- `N` unset: one reviewer per applicable dimension (skip dimensions with no touched files). -- `N` >= applicable dimensions: extra agents split the largest dimension by file group (state the split with `log`/prose; never silently drop files). -- `N` < applicable dimensions: merge adjacent dimensions into `N` buckets, preferring to keep 1 (boundaries/contracts) and 2 (security) standalone. - -Spawn all reviewers in a SINGLE message (parallel `Task` calls). Prefer the roster subagent named in -the table; for general dimensions use `general-purpose` with an explicit instruction to read the 搂3 -docs first. Pass each reviewer: the changed-file list, the base ref, its dimension checklist, and the -report-only constraint. - -## 6. Evidence & confidence gate (cut false positives) - -Tell every reviewer to apply this before returning, and re-apply it yourself when synthesizing: - -- Every `[BLOCK]` and `[WARN]` MUST cite a concrete `file:line` AND the rule doc/ADR it violates. No location or no rule = downgrade to `[INFO]` or drop it. -- Report only high-confidence findings. If unsure whether something is a real defect vs a theoretical nit, downgrade or omit - prefer few actionable findings over flooding. -- Don't invent runtime failures you haven't traced through the code. State the trigger path or don't raise it. -- Don't bikeshed and don't duplicate what tooling already enforces: oxlint (`oss-boundaries/*`, `no-any`, `consistent-type-definitions`), `pnpm check:boundaries`, and `pnpm verify` run in CI. For a suspected boundary/lint issue, say "confirm with `pnpm check:boundaries`" rather than guessing - flag only what those gates miss. -- Each reviewer self-checks before returning: every finding has evidence + a cited rule, uncertain claims downgraded, no style nitpicks lint already catches, no unverified runtime claims. - -## 7. Synthesize - -Collect all findings, dedup by `file:line`, and merge into one report. Each finding: - -- `[BLOCK]` - must fix before merge (boundary/contract break, money/authz/PII risk, missing audit on a mutation, schema drift without `pnpm regen`). -- `[WARN]` - should fix (convention violation, weak idempotency, missing test). -- `[INFO]` - FYI / hardening. - -Group by dimension, ordered BLOCK -> WARN -> INFO. Each line: -`[SEV] file:line - finding - evidence - rule/ADR cited - fix`. BLOCK/WARN without a `file:line` and a -cited rule do not ship - they were already dropped by the gate in 搂6. Lead with a one-line summary: -counts per severity + verdict. End with **APPROVED** / **CHANGES REQUESTED** and the single most -critical finding. - -If `--fix`: after the report, apply BLOCK + WARN fixes in the working tree (smallest diff that -satisfies the cited rule), then run `pnpm verify` (or `pnpm check:boundaries` for boundary fixes) and report -green/red. Leave INFO items untouched. Never commit or push. - -## Constraints - -- Read-only by default; `--fix` edits the working tree only - no commit, no push. -- Reviewers report findings; they do not edit. Only the orchestrator edits, and only under `--fix`. -- Always cite the rule doc + ADR a finding rests on - no opinions ungrounded in the conventions. -- Cap at 6 parallel reviewers. +Read [Review workflows - OSS review](../../../docs/standards/skills/review.md#oss-review) in full before acting. diff --git a/.rulesync/skills/review/SKILL.md b/.rulesync/skills/review/SKILL.md index 8011668f..0ba03ac0 100644 --- a/.rulesync/skills/review/SKILL.md +++ b/.rulesync/skills/review/SKILL.md @@ -1,107 +1,8 @@ --- name: review -description: Context-aware PR review - gathers ticket AC + PR discussion, reviews from fixed angles (security, performance, duplication, simplification, conventions, igaming fit), and returns line-anchored draft comments plus a one-sentence go/no-go. Token-budgeted so it runs fine on capped plans. Use on "review", "/review", "review this PR", optionally a PR number or "--base ". +description: Context-aware PR review with draft comments and a GO or NO-GO verdict. Use on "review", "/review", "review this PR", optionally a PR number or "--base ". --- # review -Review the current branch (or a given PR) with full task context, and return comments a human can -paste onto specific lines plus a single go/no-go sentence. Report-only: never edit, commit, or push. - -Token budget is a first-class constraint: gather context once in the main thread, spawn at most 4 -subagents (and none for small diffs), pass them pre-scoped input so they never re-do discovery. - -``` -review: -- [ ] 1. Scope the diff -- [ ] 2. Collect task context (ticket AC + PR discussion) -- [ ] 3. Review from the applicable angles (inline for small diffs, subagents otherwise) -- [ ] 4. Line-anchored comments + go/no-go -``` - -## 1. Scope the diff - -- `$ARGUMENTS` has a PR number: `gh pr diff --name-only` + keep the patch for step 3. -- Otherwise: `git diff ...HEAD --name-only --stat` (default base `dev`; `--base ` overrides). -- Empty diff: ask what to review, stop. -- Note the total changed-line count - it picks the mode in step 3. - -## 2. Collect task context - -Distill everything in this step into ONE context block of at most ~30 lines; it is the only task -context reviewers receive. - -**Ticket.** Extract a ticket key (`[A-Z][A-Z0-9]+-\d+`) from the branch name, PR title, or PR body. -If a key exists AND the session has an issue-tracker integration (an MCP tool or authenticated CLI -that can fetch it), fetch the ticket and distill: goal in one line + acceptance criteria as bullets. -No key or no integration: skip silently - never ask the user to set one up. Privacy rule: use ticket -content only to derive the goal/AC bullets; never quote raw ticket text, internal URLs, attachments, -or people's names in review output or draft comments. - -**PR discussion.** If a PR exists: `gh pr view --json title,body` and -`gh api repos/{owner}/{repo}/pulls//comments --jq '.[].body'` (plus issue comments). Distill to: -stated intent, plus any unresolved reviewer asks (so the review doesn't repeat or contradict them). -No PR yet: use the branch's commit subjects as intent. - -## 2b. Stance - assume the change is broken - -Review to falsify, not to confirm. Every reviewer (and you, on the fast path) starts from "this code does not work" and lets the diff earn correctness: - -- For each changed behavior, trace the concrete execution path with real inputs - happy path plus at least one hostile one (empty/`''`/`0`, error, unauthorized, concurrent/repeat) - until you hit a defect or prove it sound. Reading the diff hunk is never enough. -- Verify the called API actually behaves as the code assumes - open the callee or check current docs. Watch for falsy-vs-nullish, off-by-default options, swallowed rejections, partial failure mid-flow. -- Author claims prove nothing: commit message, comments, green gates, and "obviously correct" wrappers are not evidence. -- Skepticism picks what to dig into; the step-4 evidence gate still decides what becomes a finding - only a traced trigger path qualifies. - -## 3. Review angles - -Fixed angles and when they apply: - -| Angle | Applies when | Subagent | -| ----------------------------------------------------- | ---------------------------------------------------------- | ------------------- | -| Performance, duplication, simplification, conventions | always | `quality-reviewer` | -| Security & money | server code, wallet/payments/auth/admin/adapter files | `security-reviewer` | -| Contracts & boundaries | `contract/`, `schema/`, migrations, openapi, package entry | `contract-reviewer` | -| iGaming domain fit | business rules changed AND AC exists to judge against | `expert` | - -**Small-diff fast path (<= 150 changed lines): no subagents.** Read the changed files in the main -thread and apply all applicable angle checklists yourself under the 搂2b stance (each subagent's -checklist is in `.claude/agents/.md` - skim, don't spawn). This is the common case and costs a fraction of a -fan-out. - -**Larger diffs: spawn only the applicable subagents, all in ONE message (parallel).** Models are -fixed in each agent's definition (sonnet for quality/contract, opus for security/expert) - do not -override upward. Pass each reviewer: - -- base ref + the changed-file list relevant to its angle (pre-grouped - reviewers never re-scope) -- the step-2 context block + the 搂2b stance verbatim -- hard caps: read only changed files + immediate callees; max 10 findings; compact - `[SEV] file:line - finding - evidence - fix` lines, no prose; do NOT run `pnpm verify`/tests - -## 4. Synthesize and report - -Dedup findings by `file:line`. Evidence gate: a finding without a concrete `file:line` and a stated -trigger/evidence is dropped; don't repeat what lint/CI already enforces (`pnpm verify`, -`pnpm check:boundaries`); don't contradict an unresolved reviewer thread without saying so. - -Output, in order: - -1. **Draft comments** - one per finding, most important first, ready to paste on the PR line. - Plain conversational text - reviewers return internal severity tags for your ordering and the - verdict, but NEVER copy `[BLOCK]`/`[WARN]`/`[INFO]` markers into a draft comment: - - ``` - packages/core/src/wallet/service.ts:142 - Balance update reads then writes in two queries - a concurrent request can double-spend. Let's wrap it in one transaction with a guard row. - ``` - -2. **AC check** - if AC bullets exist, one line each: met / not met / not verifiable from the diff. - -3. **Verdict** - exactly one sentence: **GO** or **NO-GO** + the reason. - Example: `NO-GO - withdrawal path has a double-spend race (wallet/service.ts:142); everything else is minor.` - Merge-stoppers (money/authz/data-loss/contract break, AC not met) force NO-GO; anything else is GO. - -## Constraints - -- Read-only. No edits, no commits, no pushes, no posting comments to the PR - output drafts only. -- Max 4 subagents, one parallel batch, no re-spawns for follow-ups - resolve small doubts yourself. -- Ticket privacy rule from step 2 applies to every line of output. +Read [Review workflows - Pull request review](../../../docs/standards/skills/review.md#pull-request-review) in full before acting. diff --git a/.rulesync/subagents/contract-reviewer.md b/.rulesync/subagents/contract-reviewer.md index 0d197e30..45a7ea5c 100644 --- a/.rulesync/subagents/contract-reviewer.md +++ b/.rulesync/subagents/contract-reviewer.md @@ -15,7 +15,7 @@ Stance: assume the change is BROKEN until you trace it working - review to falsi ## Grounding -If the orchestrator passed a base ref + changed-file list, use them - do not re-scope the diff. Otherwise: `git diff origin/dev...HEAD --name-only`. READ each changed file before judging it - never infer behavior from a hunk. Compare route changes against the committed `docs/openapi.json`. Cite the rule doc (`conventions`, `docs/standards/*.md`, `db-conventions`, root `AGENTS.md`) or ADR each finding rests on. +If the orchestrator passed a base ref + changed-file list, use them - do not re-scope the diff. Otherwise: `git diff origin/dev...HEAD --name-only`. READ each changed file before judging it - never infer behavior from a hunk. Compare route changes against the committed `docs/openapi.json`. Cite the rule doc (`conventions`, `docs/standards/*.md`, root `AGENTS.md`) or ADR each finding rests on. ## Checklist @@ -35,7 +35,7 @@ If the orchestrator passed a base ref + changed-file list, use them - do not re- - [ ] Tables only in the owning module's `schema/index.ts`; no cross-module FK references (plain id columns). - [ ] Migrations generated by `pnpm regen`, never hand-edited; a migration exists for each table change. -- [ ] snake_case SQL identifiers, timestamptz datetimes (`db-conventions`). +- [ ] snake_case SQL identifiers, timestamptz datetimes (`docs/standards/database.md`). ### Patterns diff --git a/.rulesync/subagents/dev.md b/.rulesync/subagents/dev.md index 8b027525..642101d3 100644 --- a/.rulesync/subagents/dev.md +++ b/.rulesync/subagents/dev.md @@ -24,7 +24,7 @@ Your prompt contains requirements + acceptance criteria. Build to those. If the ## Before writing code -1. Read root `AGENTS.md` (decision tree, boundaries, forbidden patterns) and the sibling rules (`conventions`, `messaging-and-microservices`, `db-conventions`) plus the `docs/standards/` file matching what you are changing. Follow exactly. +1. Read root `AGENTS.md` (decision tree, boundaries, forbidden patterns) and the sibling rules (`conventions`, `messaging-and-microservices`) plus the `docs/standards/` file matching what you are changing. Follow exactly. 2. Read the touched module's `AGENTS.md` and any related `docs/adr/`. 3. Inspect current state via `oss-dev` MCP: `list-modules`, `describe-module`, `list-routes` (collision check), `query-openapi`, `get-drizzle-schema`, `propose-table-change` (before any table), `schema-get`. 4. Pick the home via the decision tree. Use the scaffolders (`pnpm gen module|route|plugin|adapter|job-worker`) - don't hand-write skeletons. diff --git a/.rulesync/subagents/module-author.md b/.rulesync/subagents/module-author.md index 470c0d56..d37522a1 100644 --- a/.rulesync/subagents/module-author.md +++ b/.rulesync/subagents/module-author.md @@ -22,7 +22,7 @@ You are an expert TypeScript / Hono / oRPC engineer implementing a module for th ## Grounding (do this first) -1. Read root `AGENTS.md` + sibling rules (`conventions`, `db-conventions`) + `docs/standards/module-structure.md`. Follow exactly. +1. Read root `AGENTS.md` + the `conventions` sibling rule + matching `docs/standards/` files, especially `database.md` and `module-structure.md`. Follow exactly. 2. Read an existing module (eg `packages/core/src/wallet/`) for the exact file shape. 3. Check current state via `oss-dev` MCP: `list-modules`, `describe-module`, `list-routes`, `query-openapi` (route collisions), `get-drizzle-schema`, `propose-table-change` (before ANY table). 4. Unanswered domain question in the brief? STOP and spawn `expert` before writing code. @@ -40,7 +40,7 @@ Creates the module as a standalone package with all required files and registers | File | What goes here | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- | -| `schema/index.ts` | Drizzle `pgTable`s (see `db-conventions`). `propose-table-change` first. | +| `schema/index.ts` | Drizzle `pgTable`s (see `docs/standards/database.md`). `propose-table-change` first. | | `contract/index.ts` | oRPC route contract + req/res Zod schemas - the source of truth. | | `schemas/index.ts` | Local Zod helpers; types via `z.infer`, never hand-written. | | `service/.service.ts` | Business logic as plain async methods. No HTTP concepts. Inject `DrizzleService` + `EventBus`. | diff --git a/.rulesync/subagents/quality-reviewer.md b/.rulesync/subagents/quality-reviewer.md index 4120b502..986de94a 100644 --- a/.rulesync/subagents/quality-reviewer.md +++ b/.rulesync/subagents/quality-reviewer.md @@ -15,8 +15,8 @@ Stance: assume the change is BROKEN until you trace it working - review to falsi ## Grounding -- Read `.claude/rules/conventions.md` IN FULL and enforce all of it - every section, not a subset. The lens checklists below are high-signal reminders, not the boundary of the review. -- When the diff touches module layering, DI, or ports, also apply `docs/standards/module-structure.md`; for SQL/Drizzle, `.claude/rules/db-conventions.md`; and the touched module's `AGENTS.md`. +- Read `conventions` in full and enforce all of it - every section, not a subset. The lens checklists below are high-signal reminders, not the boundary of the review. +- When the diff touches module layering, DI, or ports, also apply `docs/standards/module-structure.md`; for SQL/Drizzle, `docs/standards/database.md`; and the touched module's `AGENTS.md`. - Where no repo rule covers a problem, judge by established industry practice (algorithmic complexity, DB query patterns and indexing, transaction scope, React render behavior, error-handling hygiene, API design) and name the principle in the finding instead of a rule doc. - Verify before you claim: for library/framework API behavior, check current docs (context7 MCP or web search) instead of assuming from memory; use the `oss-dev` MCP tools for routes/schemas. If the orchestrator passed a ticket key and an issue-tracker tool is available, you may fetch it for acceptance criteria - never quote raw ticket text in findings. diff --git a/docs/standards/skills/delivery.md b/docs/standards/skills/delivery.md new file mode 100644 index 00000000..36adb133 --- /dev/null +++ b/docs/standards/skills/delivery.md @@ -0,0 +1,54 @@ +# Delivery workflows + +## Add feature + +Use for an OSS platform-core feature, a consumer work-order requiring `@openora/*` changes, or a standalone core feature. +Read-only until the plan is explicitly approved. + +### Workflow + +1. Resolve the input and collect scoped context from the ticket, relevant ADRs, generated contract surfaces, matching standards, source, and prior design discussion. +2. Run prompt enhancement before gathering deeply so the work remains scoped. +3. Grill every design choice that would change the build: scope, domain and seam ownership, data-model forks, regulatory and audit requirements, reuse, in-flight collisions, and config surface. +4. Present goal, acceptance criteria, locked decisions with sources, exact surface, boundary impact, risks, and a task breakdown mapped to roster agents. +5. Require explicit approval before editing. +6. Delegate implementation by slice: `expert` for requirements, `module-author` for a new module, `plugin-author` for an overlay, and `dev` for cross-module logic, contracts, services, or SDK work. +7. Ship co-located unit and integration tests with each slice. +8. Derive and run E2E cases from the acceptance criteria, including authz negatives, money/idempotency, and audit entries for mutations. +9. Send E2E and review findings back to the implementer until green. +10. Regenerate after contract or Drizzle changes, run the full verification gate, then invoke the pull-request workflow. + +### Delivery rules + +- Never write to Jira unless the user explicitly requests that exact write. +- Draft Slack notices only, and only after delivery. +- One PR per concern. +- Never push without explicit per-action confirmation. + +## Create pull request + +Use `gh` against `github.com/blurifycom/openora`. +Promotion follows `feature -> dev -> stage -> tag`. + +### Target selection + +- `dev` targets `stage`. +- `stage` is released by an explicitly requested tag, not a PR. +- `feat/*`, `fix/*`, and every other feature branch target `dev`. + +### Workflow + +1. Determine the current branch and target. +2. Inspect `git status -s` and stage only this work's files. +3. Leave foreign or pre-existing edits untouched and report them. +4. Create a lowercase conventional commit without AI trailers or sensitive data. +5. Run `pnpm verify`. +6. Report the commit SHA and stop for explicit per-action push confirmation. +7. Push only after that confirmation. +8. Reuse an existing matching PR if present; otherwise read `.github/pull_request_template.md`, complete its actual sections, then create the PR with `gh`. +9. Report the PR URL. + +### Public-record rules + +- Do not put internal URLs, secrets, tokens, customer or operator names, PII, internal hosts, or paths in commit messages, PR titles, or PR descriptions. +- Refer to tickets by bare key only. diff --git a/docs/standards/skills/prompt-enhancement.md b/docs/standards/skills/prompt-enhancement.md new file mode 100644 index 00000000..9c90f532 --- /dev/null +++ b/docs/standards/skills/prompt-enhancement.md @@ -0,0 +1,40 @@ +# Prompt enhancement + +Turn a raw request into the brief a stranger could execute before editing files. +Use this as a pre-step for fuzzy, broad, multi-part, or under-specified asks. + +## When to run + +- Run when scope, target, success criteria, or constraints are unclear. +- Skip when the request is precise and self-contained, such as a specific mechanical edit or factual question. +- Match effort to the ask - a small task needs a one-line brief, not a design document. + +## Method + +1. Restate the intent in one line. +2. Classify it as build, review, debug, refactor, docs, research, or ops. +3. For build work, use the `enhance-intent` MCP tool and stop this workflow. +4. Gather only context that changes the plan: ticket, ADRs, catalog, relevant code, and rule documents. +5. Surface only blocking ambiguities. +6. Emit the brief below. + +## Brief + +- Objective - outcome in priority order. +- Scope - included and explicitly excluded work. +- Context - load-bearing facts with source links. +- Constraints - conventions, module layering, dependency budget, and public OSS surface. +- Deliverables - concrete artifacts that prove completion. +- Guardrails - protected areas and required confirmation points. +- Open questions - only decisions that block progress. + +## Modes + +- Return for approval by default for non-trivial or ambiguous work. +- Enhance then proceed only when an orchestrator passes the completed brief to a subagent. + +## Rules + +- Preserve intent and never invent scope. +- Gather context after scoping, not before. +- Enhance once at the top of the workflow. diff --git a/docs/standards/skills/review.md b/docs/standards/skills/review.md new file mode 100644 index 00000000..90638d75 --- /dev/null +++ b/docs/standards/skills/review.md @@ -0,0 +1,79 @@ +# Review workflows + +## OSS review + +Use for a convention-grounded review of the current change set. +Report only unless `--fix` is passed. + +### Arguments + +- `--agents N` selects one to six reviewers; default one reviewer per applicable dimension. +- `--base ` changes the base; default `dev`. +- A PR number reviews that PR with `gh pr diff` and `gh pr view`. +- Paths limit the review scope. +- `--fix` permits the orchestrator to apply BLOCK and WARN fixes after reporting. + +### Workflow + +1. Scope the diff from `...HEAD`, falling back to the working tree only when the branch diff is empty. +2. Group changed files by package or domain. +3. Read each changed file and the standards governing its dimension before judging it. +4. Select only dimensions that apply: contracts and boundaries, security and money, conventions and quality, messaging seams, audit completeness, and operator fit. +5. Use the matching roster reviewer where available: `contract-reviewer`, `security-reviewer`, `quality-reviewer`, or `operator`. +6. Keep unmatched dimensions in the orchestrator rather than spawning a generic agent. +7. Fan out one parallel batch, deduplicate by `file:line`, apply the evidence gate, and return one verdict. + +### Evidence gate + +- Every BLOCK or WARN cites a concrete `file:line`, trigger path, and rule or ADR. +- Drop uncertain, theoretical, duplicate, or tooling-only findings. +- Do not report style nits already enforced by `pnpm verify` or `pnpm check:boundaries`. +- Trace called functions whenever a finding depends on their behaviour. + +### Output + +- Lead with counts by severity and a verdict. +- Order findings BLOCK, WARN, INFO. +- Use `[SEV] file:line - finding - evidence - rule or ADR - fix`. +- End with APPROVED or CHANGES REQUESTED and the most critical finding. + +### Fix mode + +- Apply only BLOCK and WARN fixes. +- Re-run the affected verification gate. +- Never commit or push. + +## Pull request review + +Use for a context-aware, report-only review of the current branch or a given PR. +Return line-anchored draft comments, acceptance-criteria status, and one GO or NO-GO sentence. + +### Workflow + +1. Scope the diff from the supplied PR or `...HEAD`, defaulting to `dev`. +2. Gather ticket acceptance criteria and unresolved PR discussion once, then distill them to a small context block. +3. Do not expose raw ticket text, internal URLs, attachments, or people's names in review output. +4. Assume each changed behaviour is broken until a concrete happy path and hostile path prove otherwise. +5. Trace empty, falsy, error, unauthorized, concurrent, and repeated inputs where applicable. +6. For a small diff, review inline from the applicable specialised-agent checklists. +7. For a larger diff, fan out only matching specialised reviewers in one parallel batch, passing the scoped files and context. +8. Deduplicate findings and discard any without a concrete location and trigger. + +### Review angles + +- Quality and conventions - always. +- Security and money - server, wallet, payment, auth, admin, or adapter changes. +- Contracts and boundaries - contract, schema, migration, OpenAPI, or package-entry changes. +- iGaming fit - business-rule changes with acceptance criteria. + +### Output + +1. Draft comments, most important first, in conversational language without internal severity markers. +2. One status line per acceptance criterion: met, not met, or not verifiable. +3. Exactly one GO or NO-GO sentence. + +### Rules + +- Review only - never edit, commit, push, or post comments. +- Cap review fan-out at four specialised agents. +- Money, authz, data-loss, contract-break, or unmet-acceptance findings force NO-GO. From 71c2752f6fbad7296092db0f79d8343f99e8508d Mon Sep 17 00:00:00 2001 From: Volodymyr Zakhovaiko Date: Fri, 31 Jul 2026 14:29:05 +0200 Subject: [PATCH 17/17] chore(react): align hooks with react compiler --- .rulesync/rules/overview.md | 2 +- .rulesync/subagents/quality-reviewer.md | 1 - docs/standards/react-sdk.md | 16 ++++++++++------ 3 files changed, 11 insertions(+), 8 deletions(-) diff --git a/.rulesync/rules/overview.md b/.rulesync/rules/overview.md index d9f09275..65e04a18 100644 --- a/.rulesync/rules/overview.md +++ b/.rulesync/rules/overview.md @@ -66,7 +66,7 @@ extensions.config.ts # the single registry of enabled plugins - **Enum / status value set** -> a values + schema + type triple on the contract surface (cross-domain: core `contracts/schemas/`; domain-local: the module's `contract/`), pgEnum derived from the tuple. `docs/standards/types.md` + `docs/standards/database.md` > Enums. - **Cross-module event** -> declare the payload in `domainEventSchemas` (`packages/core/src/contracts/schemas/events.ts`), emit via `EventBus`, subscribe with `ctx.events.on(...)`. ADR-0010; detail in `messaging-and-microservices`. - **Frontend UI** -> NOT here (headless). Consumer builds it over HTTP via `@openora/core/react`. -- **New data hook** -> `packages/core/src/react/hooks/` (domain-specific: that domain's `react/` dir). Hand-write `useMemo`/`useCallback` for stability-contract returns (consumer's React Compiler skips `node_modules`) - `docs/standards/react-sdk.md`. +- **New data hook** -> `packages/core/src/react/hooks/` (domain-specific: that domain's `react/` dir). React Compiler owns memoization - `docs/standards/react-sdk.md`. - **Operator config** (feature flags, brands, RG defaults) -> `platform-config.yaml`/`.json` via `loadPlatformConfig()` + `PlatformConfigSchema`, bound as `PLATFORM_CONFIG`. ADR-0013. - **Third-party integration** (PSP, KYC, aggregator, chat) -> adapter interface + `createToken` in `packages/core/src/contracts/adapters/.ts`, impl in the owning module's `adapters//`, bound in `plugin.ts` via `ctx.provide(TOKEN, () => new Impl())`. Never inline `fetch`/SDK calls. - **Background task** -> the `JOB_QUEUE` seam: `enqueue(queue('name'), payload, { idempotencyKey, delayMs, attempts, backoff, orderingKey })`; a worker overlay registers the handler via `ctx.jobs.worker(...)`. At-least-once: handlers idempotent (DB guard for money). ADR-0014. diff --git a/.rulesync/subagents/quality-reviewer.md b/.rulesync/subagents/quality-reviewer.md index 986de94a..0e1b60e4 100644 --- a/.rulesync/subagents/quality-reviewer.md +++ b/.rulesync/subagents/quality-reviewer.md @@ -38,7 +38,6 @@ The orchestrator passes you the base ref and changed-file list - do not re-scope - [ ] Bounded fan-out - concurrent DB work over a query result goes through `mapConcurrent(items, limit, fn)`, never an uncapped `Promise.all(rows.map(...))` (one pool connection per row starves the instance at scale). Watch for fan-out hidden behind a called method, and for oversized `IN (...)` lists. - [ ] No unbounded reads - lists paginate; no `SELECT *` of a hot table into memory to filter in JS. - [ ] Hot-path work not repeated per call when it can be computed once (schema parsing, regex compilation, config reads). -- [ ] React hooks: stability-contract returns use `useMemo`/`useCallback` (`docs/standards/react-sdk.md`). ### Duplication diff --git a/docs/standards/react-sdk.md b/docs/standards/react-sdk.md index 22e2b5a3..9c07de3a 100644 --- a/docs/standards/react-sdk.md +++ b/docs/standards/react-sdk.md @@ -5,13 +5,14 @@ Detail for the SDK line in `conventions`. Read this when adding or changing a ho Headless repo - only the SDK consumption layer lives here, no UI. - **One `useX` per concern, returning a plain object** (`{ wallet, isLoading }`). -- **Hand-write `useMemo`/`useCallback` wherever a returned value/function is part of a hook's stability contract** - the OPPOSITE of the consumer-app rule, because the consumer's React Compiler does not reprocess pre-built `node_modules`. Keep hooks Rules-of-React compliant so the consumer's compiler can optimize callers. +- **React Compiler owns memoization.** Keep hook returns simple unless a compiler-unsupported semantic requirement demands otherwise. - **Server state is not client state** - key/cache/invalidate via the query lib, never a raw `useEffect(fetch)`. - **A published SDK export annotates its return type** - it is the public contract; an inferred return silently leaks a refactor as a downstream breaking change (see `functions.md`). - `react` never imports `server` or a module (lint: `no-react-to-runtime`). +Avoid fetching server state in an effect or leaking an inferred public return type. + ```tsx -// bad - fetch in an effect, a new object every render, inferred public return type export function useWallet(userId: string) { const [wallet, setWallet] = useState(); useEffect(() => { @@ -21,12 +22,15 @@ export function useWallet(userId: string) { }, [userId]); return { wallet, refetch: () => {} }; } -// good - query lib owns server state, stability contract hand-memoized, return type explicit +``` + +Use the query library for server state and declare the public return type. + +```tsx export function useWallet(userId: string): UseWalletResult { const { data, isLoading, refetch } = useQuery(walletQuery(userId)); - const reload = useCallback(() => void refetch(), [refetch]); - return useMemo(() => ({ wallet: data, isLoading, reload }), [data, isLoading, reload]); + return { wallet: data, isLoading, refetch }; } ``` -Canonical hooks to copy: `packages/core/src/react/hooks/use-paginated-list.ts` (query + stable return), `use-event-stream.ts` (realtime SSE subscription). +Canonical hooks to copy: `packages/core/src/react/hooks/use-paginated-list.ts` (query), `use-event-stream.ts` (realtime SSE subscription).