Repository navigation
docs(review): rule 16 — coherence is not evidence; make the mechanism predict #2795
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Tests | |
| on: | |
| push: | |
| branches: [ main, v1.0.x ] | |
| # No branches filter on pull_request: stacked PRs (base = another feature | |
| # branch) previously got ZERO test runs — every number on them was | |
| # single-sourced to the author's local sweep, and the 2026-08-22 threading | |
| # train shipped two real failures that only surfaced once CI was dispatched | |
| # by hand. Merge-to-main guards (base-freshness, version-guard) stay scoped | |
| # to main; the test tiers run for every PR regardless of base. | |
| pull_request: | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| # The push fallback is github.sha, NOT github.ref. On a push event `github.ref` | |
| # is the *branch*, so every commit merged to main shared one concurrency group | |
| # and each merge cancelled the previous commit's still-running verification. | |
| # Measured across 39 Tests runs on main (2026-08-04 → 08-06): 18 cancelled. | |
| # | |
| # The loss is ordered by job depth, not random, which is what makes it worth | |
| # fixing rather than tolerating: | |
| # | |
| # Chart Lint no needs, ~8s survived 12 of 14 | |
| # Test & Coverage no needs, ~5.5min survived 3 of 14 | |
| # Service Tests needs:test, starts ~5.5min in survived 0 of 14 | |
| # | |
| # Service Tests is the ADR-009 Tier 1 real-DB tier — the deepest job, and the | |
| # only one that never once completed in a cancelled run. So the tier main was | |
| # silently skipping is the tier hardest to reproduce anywhere else. | |
| # | |
| # Per-SHA grouping lets every commit finish; PR runs still supersede correctly | |
| # because pull_request.number short-circuits before the fallback is read. | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} | |
| cancel-in-progress: true | |
| jobs: | |
| test: | |
| name: Test & Coverage | |
| runs-on: ubuntu-latest | |
| env: | |
| JWT_SECRET: test-jwt-secret | |
| SENDGRID_API_KEY: fake-api-key | |
| SENDGRID_FROM_EMAIL: test@example.com | |
| FRONTEND_URL: http://localhost:3000 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v3 | |
| with: | |
| submodules: recursive | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| cache-dependency-path: '**/package-lock.json' | |
| - name: Install dependencies | |
| run: | | |
| npm ci --include=dev | |
| cd backend | |
| npm ci --include=dev | |
| cd ../frontend | |
| npm install --legacy-peer-deps | |
| cd ../commonly-mcp | |
| npm ci --include=dev | |
| cd ../cli | |
| npm ci --include=dev | |
| cd .. | |
| - name: Verify moltbot tool contract | |
| run: npm run verify:moltbot-tools | |
| # Executes the litellm boot block's patch runner against a two-tree fixture, | |
| # so the 79-hour SEV class (patcher and proxy resolving `import litellm` to | |
| # different copies) fails here instead of in a crash-looping pod. Needs a | |
| # python3 on PATH — ubuntu-latest ships one; if that ever stops being true | |
| # the guard says so by name rather than passing quietly. | |
| - name: Verify litellm patch runner | |
| run: npm run verify:litellm-patch-runner | |
| - name: Backend TypeScript check | |
| run: | | |
| cd backend | |
| npm run tsc:check | |
| - name: Frontend TypeScript check | |
| run: | | |
| cd frontend | |
| npm run typecheck | |
| # packages/types has zero dependencies and zero external imports, so it | |
| # needs no install of its own — it typechecks with the compiler the | |
| # backend step already installed. Gated now because it is green now; | |
| # that is the only moment a gate is free. | |
| # | |
| # packages/commonly-apps is deliberately NOT gated here. It defines the | |
| # same tsc:check script and is equally uninvoked, but it needs @types/node | |
| # and is not npm ci'd by this job, so adding it means adding an install. | |
| # Its actual state is unknown, not assumed green. Separate change. | |
| - name: Shared types TypeScript check | |
| run: | | |
| cd packages/types | |
| ../../backend/node_modules/.bin/tsc --noEmit -p tsconfig.json | |
| - name: Run backend tests with coverage | |
| run: | | |
| cd backend | |
| npx jest --coverage --forceExit | |
| - name: Run frontend tests with coverage | |
| run: | | |
| cd frontend | |
| npx jest --coverage --watchAll=false --forceExit | |
| - name: Run commonly-mcp tests | |
| run: | | |
| cd commonly-mcp | |
| npm test | |
| - name: Run CLI tests | |
| run: | | |
| cd cli | |
| npm test | |
| - name: Upload coverage report | |
| uses: actions/upload-artifact@v4 | |
| with: | |
| name: coverage-report | |
| path: backend/coverage/ | |
| service-test: | |
| name: Service Tests (Tier 1 — real DBs) | |
| runs-on: ubuntu-latest | |
| needs: test | |
| services: | |
| mongo: | |
| image: mongo:7 | |
| ports: ["27017:27017"] | |
| options: >- | |
| --health-cmd "mongosh --eval 'db.runCommand(\"ping\").ok'" | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| postgres: | |
| image: postgres:16 | |
| ports: ["5432:5432"] | |
| env: | |
| POSTGRES_USER: postgres | |
| POSTGRES_PASSWORD: postgres | |
| POSTGRES_DB: commonly-test | |
| options: >- | |
| --health-cmd pg_isready | |
| --health-interval 10s | |
| --health-timeout 5s | |
| --health-retries 5 | |
| env: | |
| JWT_SECRET: test-jwt-secret | |
| SENDGRID_API_KEY: fake-api-key | |
| SENDGRID_FROM_EMAIL: test@example.com | |
| FRONTEND_URL: http://localhost:3000 | |
| INTEGRATION_TEST: "true" | |
| MONGO_URI: mongodb://localhost:27017/commonly-test | |
| PG_HOST: localhost | |
| PG_PORT: "5432" | |
| PG_DATABASE: commonly-test | |
| PG_USER: postgres | |
| PG_PASSWORD: postgres | |
| PG_SSL_ENABLED: "false" | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v3 | |
| - name: Set up Node.js | |
| uses: actions/setup-node@v4 | |
| with: | |
| node-version: '22' | |
| cache: 'npm' | |
| cache-dependency-path: '**/package-lock.json' | |
| - name: Install backend dependencies | |
| run: cd backend && npm ci --include=dev | |
| - name: Run service tests (Tier 1) | |
| run: | | |
| cd backend | |
| npx jest --testPathPattern="__tests__/service" --forceExit --runInBand | |
| chart-lint: | |
| name: Chart Lint (Tier 1.5) | |
| runs-on: ubuntu-latest | |
| env: | |
| # Pin CRD schemas to a known commit to avoid drift. | |
| # Update manually by running: gh api /repos/datreeio/CRDs-catalog/commits/main --jq .sha | |
| CRDS_CATALOG_SHA: 15e9b72701ce617b0ac15da611a7fb2e54b980dd | |
| KUBECONFORM_VERSION: v0.7.0 | |
| YQ_VERSION: v4.44.3 | |
| steps: | |
| - name: Checkout code | |
| uses: actions/checkout@v3 | |
| - name: Install Helm | |
| uses: azure/setup-helm@v4 | |
| with: | |
| version: v3.14.0 | |
| - name: Install kubeconform | |
| run: | | |
| curl -sSL "https://github.com/yannh/kubeconform/releases/download/${KUBECONFORM_VERSION}/kubeconform-linux-amd64.tar.gz" -o kc.tgz | |
| sudo tar -xzf kc.tgz -C /usr/local/bin/ kubeconform | |
| kubeconform -v | |
| - name: Install yq | |
| run: | | |
| curl -sSL "https://github.com/mikefarah/yq/releases/download/${YQ_VERSION}/yq_linux_amd64" -o yq | |
| chmod +x yq | |
| sudo mv yq /usr/local/bin/yq | |
| yq --version | |
| - name: Render dev chart | |
| run: | | |
| helm template commonly-dev k8s/helm/commonly \ | |
| -f k8s/helm/commonly/values.yaml \ | |
| -f k8s/helm/commonly/values-dev.yaml \ | |
| > /tmp/dev-rendered.yaml | |
| - name: Lint rendered dev chart | |
| run: | | |
| kubeconform --strict --summary -output text \ | |
| --schema-location "https://raw.githubusercontent.com/datreeio/CRDs-catalog/${CRDS_CATALOG_SHA}/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json" \ | |
| --schema-location default \ | |
| /tmp/dev-rendered.yaml | |
| - name: Assert reliability rollout and scheduling invariants | |
| run: | | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "backend") | .spec.strategy.type' /tmp/dev-rendered.yaml)" = "RollingUpdate" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "backend") | .spec.strategy.rollingUpdate.maxUnavailable' /tmp/dev-rendered.yaml)" = "0" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "backend") | .spec.strategy.rollingUpdate.maxSurge' /tmp/dev-rendered.yaml)" = "1" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "backend") | .spec.template.spec.volumes | map(select(has("persistentVolumeClaim"))) | length' /tmp/dev-rendered.yaml)" = "0" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "redis") | .spec.template.spec.nodeSelector.pool' /tmp/dev-rendered.yaml)" = "dev" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "redis") | .spec.template.spec.tolerations | map(select(.key == "workload-tier")) | length' /tmp/dev-rendered.yaml)" = "0" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "redis") | .spec.template.spec.tolerations | map(select(.key == "pool" and .value == "dev" and .effect == "NoSchedule")) | length' /tmp/dev-rendered.yaml)" = "1" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "backend") | .spec.minReadySeconds' /tmp/dev-rendered.yaml)" = "10" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "backend") | .spec.template.spec.terminationGracePeriodSeconds' /tmp/dev-rendered.yaml)" = "30" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "backend") | .spec.template.spec.containers[] | select(.name == "backend") | .readinessProbe.httpGet.path' /tmp/dev-rendered.yaml)" = "/api/health/ready" | |
| test "$(yq 'select(.kind == "Deployment" and .metadata.name == "backend") | .spec.template.spec.containers[] | select(.name == "backend") | .lifecycle.preStop.exec.command[2]' /tmp/dev-rendered.yaml)" = "sleep 10" | |
| - name: Render and lint prod chart (if values-prod.yaml exists) | |
| run: | | |
| if [ -f k8s/helm/commonly/values-prod.yaml ]; then | |
| helm template commonly-prod k8s/helm/commonly \ | |
| -f k8s/helm/commonly/values.yaml \ | |
| -f k8s/helm/commonly/values-prod.yaml \ | |
| | kubeconform --strict --summary -output text \ | |
| --schema-location "https://raw.githubusercontent.com/datreeio/CRDs-catalog/${CRDS_CATALOG_SHA}/{{.Group}}/{{.ResourceKind}}_{{.ResourceAPIVersion}}.json" \ | |
| --schema-location default | |
| else | |
| echo "values-prod.yaml not present yet (lands in ADR-009 Phase 5); skipping prod lint." | |
| fi |