Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 62 additions & 0 deletions .changeset/verification-pipeline-repair.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
---
'hotcrm': patch
---

Repair the verification pipeline: fake-green CI checks, a dead e2e suite, and
the runtime test coverage gaps (#495).

**CI checks that checked nothing.** `code-quality.yml`'s three greps scanned
`packages/` — a directory this repo has never had — each with
`continue-on-error: true`. They are replaced by `scripts/check-source-hygiene.mjs`,
which scans the real tree (`src`, `test`, `e2e`, `scripts`), fails on a hit, and
fails loudly if a scanned directory ever disappears. `deploy-docs.yml` copied
`QUICKSTART.md` and `PROJECT_SUMMARY.md` unguarded — neither exists, so every
triggering push failed the workflow. `continue-on-error` is off `pnpm lint` in
both workflows (`objectstack lint` already exits 0 on warnings, so the flag only
hid real errors). The orphaned `.eslintrc.json` is removed — no `eslint` package
was installed anywhere to read it — and CONTRIBUTING now says what `pnpm lint`
actually does. `.github/labeler.yml` had seven rules, three of which pointed at
paths that have never existed and one (`ui`) at a label the repo does not have;
globs are repointed and `test/labeler-config.test.ts` fails on a glob that
matches nothing. `apps/docs` (Next.js, 231 mdx pages, its own lockfile) is
compiled by a new `docs-app.yml` workflow — nothing built it before.

**The e2e suite could not run.** `playwright.config.ts` pointed `baseURL` at
port 4004 while the server serves 4001, declared no `webServer`, and no workflow
ran playwright. Both specs also treated the data API's 401 as a pass, so even on
the right port they could only prove a route was mounted. The suite now boots the
server itself, authenticates for real via a shared `globalSetup`, and asserts
unconditionally — including that the data API *is* gated, which the old
`[200, 401, 403]` assertion would have let a public-data regression through.
`retries`/`trace` are configured for CI and `e2e.yml` runs it. 11 specs pass
against a cold database.

**Typecheck blind spots.** `tsconfig.json` covered only `src/`, so `test/`,
`e2e/` and `scripts/` — including the 606-line analytics-reconcile tool — were
never typechecked. Widening `include` surfaced 45 real errors, all fixed; the
module mode moves to `preserve`/`bundler` (nothing here is emitted by tsc) and
`noEmit` is pinned so no stray `tsc` can scatter output into the `dist/` the
marketplace publishes. `tsx` is now a real dependency behind
`pnpm reconcile:analytics` — the command the script documented could not run.
`scripts/wow1-live-schema.sh` preflights the `ai` capability it needs and
explains why a local server does not provide it.

**Runtime coverage.** Hooks went from 4 of 24 tested to 24 of 24, and flows from
3 of 20 to 17 of 20 (all six scheduled sweeps included). Statement coverage of
the hook handlers goes from 23% to 95%, functions from 22% to 95%, and
`vitest.config.ts` gains thresholds set just under those numbers. Shared
harnesses replace three divergent copies of the in-memory data engine and add the
query operators the old ones lacked — an equality-only engine silently matches
nothing for the `$lt`/`$nin` filters every scheduled sweep uses, so those flows
could not have been tested against it. `test/runtime-coverage.test.ts` fails when
a hook or flow arrives with no runtime test.

**Defect these tests surfaced.** Conditional edges nested inside a `loop` body
never evaluate: `applyConversionsToFlow` rewrites a bare string condition into a
CEL envelope only for a flow's top-level edges, so a loop-nested condition falls
through to the engine's legacy path and is string-compared
(`'existingStallTask' === 'null'` → false). `opportunity_stagnation`,
`contract_renewal` and `campaign_enrollment` are inert past that gate. The
behaviour is pinned and documented in `test/flow-scheduled.test.ts` rather than
fixed here — the fix changes what these production sweeps do (they would begin
creating tasks, opportunities and notifications) and belongs in its own change.
24 changes: 0 additions & 24 deletions .eslintrc.json

This file was deleted.

41 changes: 30 additions & 11 deletions .github/labeler.yml
Original file line number Diff line number Diff line change
@@ -1,30 +1,49 @@
# Configuration for PR labeler
# Labels PRs based on changed files
# Labels PRs based on changed files.
#
# Two invariants, both of which the previous revision broke:
#
# 1. Every glob must match something in the tree. `backend` pointed at
# `src/server.ts`, `src/engine/**` and `src/triggers/**`; `ui` pointed at
# `src/ui/**` and `tailwind.config.cjs`; `metadata` pointed at
# `src/metadata/**`. None of those paths have ever existed in this repo.
# `test/labeler-config.test.ts` now fails the suite on a glob that matches
# no file.
#
# 2. Every label key must already exist in the repository — `actions/labeler`
# does not create them. The old `ui:` key had no matching repo label, so
# even a corrected glob could not have applied it. The keys below are
# exactly the six labels that exist today: documentation, dependencies,
# ci/cd, backend, metadata, configuration. Adding a key here means creating
# the label in repo settings first.

documentation:
- changed-files:
- any-glob-to-any-file: ['docs/**', '*.md', 'README*', 'CONTRIBUTING*']
- any-glob-to-any-file: ['docs/**', 'apps/docs/**', 'src/docs/**', '*.md', 'README*', 'CONTRIBUTING*']

dependencies:
- changed-files:
- any-glob-to-any-file: ['package.json', 'package-lock.json']
- any-glob-to-any-file: ['package.json', 'package-lock.json', 'pnpm-lock.yaml', 'apps/docs/package.json', 'apps/docs/pnpm-lock.yaml']

# CI plumbing and the verification pipeline itself — workflows, helper scripts,
# and the test suites those workflows run.
ci/cd:
- changed-files:
- any-glob-to-any-file: ['.github/**']
- any-glob-to-any-file: ['.github/**', 'scripts/**', 'test/**', 'e2e/**']

# Server-side behaviour: hook handlers, automation flows, and invocable
# actions. This is the code that actually executes at request time.
backend:
- changed-files:
- any-glob-to-any-file: ['src/server.ts', 'src/engine/**', 'src/triggers/**', 'src/actions/**']

ui:
- changed-files:
- any-glob-to-any-file: ['src/ui/**', 'tailwind.config.cjs']
- any-glob-to-any-file: ['src/actions/**', 'src/flows/**', 'src/hooks/**', 'src/objects/*.hook.ts']

# Declarative metadata: schema, the security posture layered on it, and the
# UI surfaces (apps, pages, views, dashboards, reports, datasets) that are
# themselves just metadata in ObjectStack.
metadata:
- changed-files:
- any-glob-to-any-file: ['src/metadata/**']
- any-glob-to-any-file: ['src/objects/**', 'src/profiles/**', 'src/sharing/**', 'src/skills/**', 'src/data/**', 'src/apps/**', 'src/pages/**', 'src/views/**', 'src/interfaces/**', 'src/dashboards/**', 'src/reports/**', 'src/datasets/**', 'src/translations/**', 'objectstack.manifest.json']

configuration:
- changed-files:
- any-glob-to-any-file: ['*.config.js', '*.config.ts', 'tsconfig.json', '.gitignore', '.env*']
- any-glob-to-any-file: ['*.config.ts', 'objectstack.config.ts', 'tsconfig.json', '.gitignore', '.changeset/config.json']
25 changes: 21 additions & 4 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,38 @@ jobs:
- name: Check the StackBlitz npm lockfile is in sync
run: node scripts/check-stackblitz-lock.mjs

- name: Source hygiene (console.log / TODO / file size)
run: node scripts/check-source-hygiene.mjs

- name: Validate metadata (protocol/schema)
run: pnpm run validate

- name: Lint metadata conventions (advisory)
# `objectstack lint` exits 0 on warnings/suggestions and non-zero only on
# real errors — `continue-on-error` was hiding exactly the failures worth
# gating on.
- name: Lint metadata conventions
run: pnpm run lint
continue-on-error: true

- name: TypeScript type checking
run: pnpm run typecheck

- name: Build project
run: pnpm run build

- name: Run tests
run: pnpm test
# `--coverage` so the thresholds in vitest.config.ts actually gate. They
# are set just under the measured numbers for the hook handlers, which
# went from 23% statements / 22% functions to 95% / 95% when the runtime
# suites landed.
- name: Run tests with coverage
run: pnpm run test:coverage

- name: Upload coverage report
if: ${{ !cancelled() }}
uses: actions/upload-artifact@v7
with:
name: coverage
path: coverage/
retention-days: 7

- name: Upload build artifacts
if: matrix.node-version == '22.x'
Expand Down
48 changes: 18 additions & 30 deletions .github/workflows/code-quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ on:
paths:
- '**.ts'
- '**.js'
- '**.mjs'
- '**.json'
- '**.yml'
- '**.yaml'
Expand All @@ -14,16 +15,16 @@ jobs:
quality-check:
name: Quality Checks
runs-on: ubuntu-latest

permissions:
contents: read

steps:
- name: Checkout code
uses: actions/checkout@v7
with:
fetch-depth: 0

- name: Setup pnpm
uses: pnpm/action-setup@v4

Expand All @@ -32,39 +33,26 @@ jobs:
with:
node-version: '22.x'
cache: 'pnpm'

- name: Install dependencies
run: pnpm install --frozen-lockfile

- name: Check for console.log statements
run: |
if grep -r "console\.log" packages/ --include="*.ts" --exclude-dir=node_modules --exclude-dir=__tests__; then
echo "Warning: console.log statements found in source code"
echo "Please use proper logging instead"
# This is just a warning, not failing the build
else
echo "No console.log statements found"
fi
continue-on-error: true


# Replaces three inline greps that scanned `packages/` — a directory this
# repo does not have — each with `continue-on-error: true`. The script
# scans the real tree (src, test, e2e, scripts) and fails on a hit; it
# also fails loudly if a scanned directory ever disappears, so the check
# can never silently go vacuous again.
- name: Source hygiene (console.log / TODO / file size)
run: node scripts/check-source-hygiene.mjs

- name: Check TypeScript compilation
run: pnpm run typecheck

- name: Validate metadata (protocol/schema)
run: pnpm run validate

- name: Lint metadata conventions (advisory)
# `objectstack lint` exits 0 on warnings and suggestions and non-zero only
# on real errors, so `continue-on-error` was suppressing nothing but the
# errors we actually want to hear about.
- name: Lint metadata conventions
run: pnpm run lint
continue-on-error: true

- name: Check for TODO comments
run: |
echo "Checking for TODO/FIXME comments..."
grep -r "TODO\|FIXME" packages/ --include="*.ts" --exclude-dir=node_modules || echo "No TODO/FIXME found"
continue-on-error: true

- name: Check file sizes
run: |
echo "Checking for large files..."
find packages/ -type f -size +100k -exec ls -lh {} \; || echo "No large files found"
continue-on-error: true
13 changes: 7 additions & 6 deletions .github/workflows/deploy-docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ on:
paths:
- 'docs/**'
- 'README.md'
- 'QUICKSTART.md'
- 'PROJECT_SUMMARY.md'
workflow_dispatch:

jobs:
Expand All @@ -32,12 +30,15 @@ jobs:

- name: Create documentation site
run: |
set -euo pipefail
mkdir -p _site
# QUICKSTART.md and PROJECT_SUMMARY.md were copied here unguarded and
# have never existed in this repo, so every triggering push failed the
# deploy. `docs/` is copied without `|| true` for the same reason: if
# the documentation tree vanishes we want to hear about it.
cp README.md _site/index.md
cp QUICKSTART.md _site/
cp PROJECT_SUMMARY.md _site/
cp -r docs/* _site/ 2>/dev/null || true

cp -r docs/. _site/

# Create a simple index.html that redirects to README
cat > _site/index.html << 'EOF'
<!DOCTYPE html>
Expand Down
61 changes: 61 additions & 0 deletions .github/workflows/docs-app.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
name: Docs App

# `apps/docs` is a full Next.js + Fumadocs site (231 mdx pages) with its own
# package.json and its own lockfile — it is NOT part of the root pnpm project,
# so `pnpm install` / `pnpm typecheck` / `pnpm build` at the root have never
# touched a single file of it. A broken import, a bad mdx frontmatter block, or
# a dead internal link could land on main with every check green.
#
# This workflow is the only thing that compiles it.

on:
push:
branches: [ main, develop ]
paths:
- 'apps/docs/**'
- '.github/workflows/docs-app.yml'
pull_request:
branches: [ main, develop ]
paths:
- 'apps/docs/**'
- '.github/workflows/docs-app.yml'
workflow_dispatch:

jobs:
build:
name: Typecheck and Build
runs-on: ubuntu-latest

permissions:
contents: read

defaults:
run:
working-directory: apps/docs

steps:
- name: Checkout code
uses: actions/checkout@v7

- name: Setup pnpm
uses: pnpm/action-setup@v4

- name: Setup Node.js
uses: actions/setup-node@v7
with:
node-version: '22.x'
cache: 'pnpm'
cache-dependency-path: apps/docs/pnpm-lock.yaml

- name: Install dependencies
run: pnpm install --frozen-lockfile

# `fumadocs-mdx && next typegen && tsc --noEmit` — generates the mdx
# collection types and the route types first, then typechecks against them.
- name: Typecheck
run: pnpm run types:check

- name: Build
run: pnpm run build
env:
NEXT_TELEMETRY_DISABLED: '1'
Loading
Loading