lint:meta rules
ESLint enforces architecture inside TypeScript modules. lint:meta catches repo-level drift ESLint cannot see: unpinned GitHub Actions, env cascade gaps, forbidden inline disables, cross-repo imports, and missing test siblings. It runs inside bun run validate.
Merge gate stack
Section titled “Merge gate stack”typecheck → ESLint → lint:meta → knip → testsapps/ui rules
Section titled “apps/ui rules”Rule ID
CI-critical
What it guards
package-json-exact-depsno
dependencies and devDependencies must use exact versions; peerDependencies must use caret (^).
no-overlapping-libsno
package.json must not list forbidden overlapping library pairs.
Rule ID
CI-critical
What it guards
github-actions-permissionsno
GitHub Actions workflows require permissions block and SHA-pinned uses: refs.
github-actions-permissions:verifyno
Pinned action SHAs resolve on github.com (lint:meta:verify only).
github-actions-timeout-requiredno
GitHub Actions jobs require an explicit timeout-minutes (reusable-workflow calls exempt).
github-actions-bun-cacheno
Workflows running bun install must cache ~/.bun/install/cache.
github-actions-concurrency-explicitno
Workflows with a concurrency block must set cancel-in-progress explicitly.
github-actions-expression-syntaxno
Every expression opener in a workflow must be a well-formed Actions expression.
github-actions-service-image-digest-pinno
Workflow service/container images must be pinned by @sha256 digest, not tag alone.
pre-push-ci-parityno
CI workflow must include every command listed in scripts/ci/pre-push.manifest.json.
tofu-bootstrap-hardeningno
infra/bootstrap must keep its hardening invariants: server lifecycle guard, no world-open variable defaults, no curl-pipe-sh, version-pinned required_providers.
engine-pin-parityno
Node and Bun version pins must stay aligned across .nvmrc, package.json, Docker, and CI.
dockerfile-base-image-sha-pinno
Dockerfile base images must be pinned by @sha256 digest, not tag alone.
Rule ID
CI-critical
What it guards
env-cascade-driftno
Vite env keys must align across schema.ts, .env.example, and vite-env.d.ts.
env-no-direct-import-meta-envno
Single entry point for env: every source file outside env.loader.ts must import the typed `env` object instead of reading `import.meta.env` directly.
Rule ID
CI-critical
What it guards
generated-artifact-contractno
Generated ACL types and OpenAPI schema files must exist with required banner text.
modulepreload-size-limit-coverageno
.size-limit.json must include globs for all modulepreload entry chunks.
Rule ID
CI-critical
What it guards
canonical-helpers-single-homeno
Helpers in the canonical registry must only be declared in their single source-of-truth file.
docs-no-retired-credentialsno
Documentation prose must not reference retired default credentials.
i18n-locale-keys-usedno
Locale keys defined in en/*.json must be referenced in src (dynamic t() prefixes exempt).
forbidden-textno
Source files must not contain inline lint/TS suppressions, raw HTML, direct env access, raw fetch, or banned Tailwind dark-mode variant classes.
no-inline-lint-disableno
Inline ESLint disables are not allowed.
no-ts-ignoreno
TypeScript suppression comments are not allowed.
no-dangerous-htmlno
Raw HTML rendering requires a dedicated sanitizer and security review.
env-accessno
Read Vite env through src/lib/env only.
no-raw-fetchno
Use the typed apiClient; raw fetch is restricted to src/lib/api/openapi.
no-inline-object-castno
Casting to an inline object type (`as { … }`) skips validation.
no-sleep-in-e2eno
Wall-clock sleeps flake under CI load and tax every run.
no-hardcoded-iso-dates-in-fixturesno
Hardcoded ISO timestamps in shared factories/e2e age out.
no-dark-variantno
The `dark:` Tailwind variant is banned.
no-cross-repo-importyes
Relative imports must stay inside apps/ui; no backend or infra source paths.
no-raw-role-literalno
Use ROLE.* from acl.types instead of raw owner/admin/member/viewer string literals.
no-raw-fetch-scriptsno
Scripts must not call global fetch except github-actions-permissions.ts (lint:meta --verify SHA check).
queries-no-silent-error-swallowno
*.queries.ts files must not silently swallow query errors as `null`. Let the typed error propagate so consumers can distinguish auth from outage; opt-out per-catch with `// allow-silent: <reason>` when an explicit null is genuinely the right contract.
Rule ID
CI-critical
What it guards
logic-files-require-test-siblingno
Logic modules must ship with a colocated *.test.ts or *.test.tsx sibling.
lint-meta-rules-self-coveredno
Every lint-meta rule module must re-export its check function from cli.ts and carry a describe() test block — the guardrails must themselves be guarded.
test-files-require-source-siblingno
Colocated test files must mirror a source sibling (no orphaned tests).
skipped-tests-need-trackingno
Skipped tests (.skip/.only/xit/xdescribe) must carry an issue URL or TODO(@owner) so the debt has a tracked owner.
Rule ID
CI-critical
What it guards
eslint-config-no-warnno
ESLint severities must be "error" or "off", not "warn".
eslint-ban-type-assertionsno
ESLint must pin @typescript-eslint/consistent-type-assertions to assertionStyle "never"; disabling it requires an audited eslint-meta-allow-assertion-exemption marker.
tsconfig-include-paths-existno
Literal tsconfig include/files entries must point at files that exist (globs exempt); checks this app and sibling apps.
eslint-plugin-contract-parityno
Every installed @boring-stack-pkg eslint plugin must appear in AGENT_CONTRACT.md, and vice versa.
apps/api rules
Section titled “apps/api rules”Rule ID
CI-critical
What it guards
package-json-exact-depsno
dependencies and devDependencies must use exact versions (no ranges).
no-overlapping-libsno
package.json must not list forbidden overlapping library pairs.
package-override-parityno
package.json overrides must be reflected in the app's own bun.lock, mirrored by sibling apps that resolve the same package, and each carry a `//overrides` entry documenting why it is pinned.
shared-tool-version-parityno
Shared dev tooling (ESLint, TypeScript, Prettier, knip, …) must be pinned to the same version in every app that declares it.
Rule ID
CI-critical
What it guards
github-actions-permissionsno
GitHub Actions workflows require permissions block and SHA-pinned uses: refs.
github-actions-permissions:verifyno
Pinned action SHAs resolve on github.com (lint:meta:verify only).
github-actions-timeout-requiredno
GitHub Actions jobs require an explicit timeout-minutes (reusable-workflow calls exempt).
github-actions-bun-cacheno
Workflows running bun install must cache ~/.bun/install/cache.
github-actions-concurrency-explicitno
Workflows with a concurrency block must set cancel-in-progress explicitly.
github-actions-paths-filter-parityno
Workflows pairing push.paths with a dorny/paths-filter gate must keep the two path sets mutually covered.
github-actions-pip-install-pinnedno
Workflow pip install steps must pin package versions with == so CI tools cannot drift with PyPI releases.
github-actions-runner-pinnedno
Workflows must pin runner images to an explicit OS version instead of floating *-latest labels.
github-actions-security-no-cancelno
Security scan workflows (*-security-{sast,secrets,deps}) must set concurrency cancel-in-progress: false so no pushed ref goes unscanned.
github-actions-expression-syntaxno
Every expression opener in a workflow must be a well-formed Actions expression.
github-actions-service-image-digest-pinno
Workflow service/container images must be pinned by @sha256 digest, not tag alone.
pre-push-ci-parityno
CI workflow must include every command listed in scripts/ci/pre-push.manifest.json.
security-scanner-version-parityno
All security workflows must pin a single gitleaks version and a single semgrep image, and scripts/ci/pre-push-security.sh must carry a local-vs-CI version-parity check for every scanner the workflows pin.
tofu-bootstrap-hardeningno
infra/bootstrap must keep its hardening invariants: server lifecycle guard, no world-open variable defaults, no curl-pipe-sh, version-pinned required_providers.
engine-pin-parityno
Bun version pin must stay aligned across package.json, Docker, and CI.
dockerfile-base-image-sha-pinno
Dockerfile base images must be pinned by @sha256 digest, not tag alone.
Rule ID
CI-critical
What it guards
env-cascade-driftno
TypeBox env schema keys must align with .env.example documentation.
env-no-direct-process-envno
Single entry point for env: every source file outside validate.ts must import the typed `env` object instead of reading `process.env` directly.
Rule ID
CI-critical
What it guards
generated-artifact-contractno
Sibling apps/ui generated ACL and OpenAPI files must carry required banner text.
Rule ID
CI-critical
What it guards
forbidden-textno
Source files must not contain inline lint/TS suppression comments.
no-inline-lint-disableno
Inline ESLint disables are not allowed.
no-ts-ignoreno
TypeScript suppression comments are not allowed.
canonical-helpers-single-homeno
Helpers in the canonical registry must only be declared in their single source-of-truth file.
docs-no-retired-credentialsno
Documentation prose must not reference retired default credentials.
external-client-timeoutno
SDK clients (Stripe/OpenAI/Anthropic) need a timeout option; email transports (Resend/SendGrid/nodemailer) must be bounded; fetch() in src needs an AbortSignal.
no-raw-role-literalno
Use ROLE.* from acl.constants.ts instead of raw owner/admin/member/viewer string literals.
schema-enum-field-consistencyno
A fixed-value field typed as t.Union([t.Literal(...)]) in one schema must not be t.String() in another schema in the same file — keep the generated API client precise.
audit-log-read-account-scopedno
Queries filtering auditLog by userId must also reference auditLog.targetAccountId — userId-only reads bleed a multi-account user's events across tenant boundaries.
Rule ID
CI-critical
What it guards
routes-require-test-siblingno
Route modules must ship with a matching HTTP-level test under tests/api/.
logic-files-require-test-siblingno
Logic modules must ship with a matching tests/**/*.test.ts sibling.
lint-meta-rules-self-coveredno
Every lint-meta rule module must re-export its check function from cli.ts and carry a describe() test block — the guardrails must themselves be guarded.
skipped-tests-need-trackingno
Skipped tests (.skip/.only/xit/xdescribe) must carry an issue URL or TODO(@owner) so the debt has a tracked owner.
security-spec-no-silent-bailno
security-spec tests must fail when a dependency is missing: no silent-bail guards (requireDb/requireValkey/isDbAvailable/isValkeyReachable) and no skipped cases.
security-spec-requires-controlno
Every describe block in security-spec must declare its own `control:` case that passes, so an expected failure is distinguishable from a broken fixture.
touch-tests-toono
Modified logic/route files must include a matching test change (opt-in via LINT_META_TOUCHED_BASE).
Rule ID
CI-critical
What it guards
eslint-config-no-warnno
ESLint severities must be "error" or "off", not "warn".
eslint-ban-type-assertionsno
ESLint must pin @typescript-eslint/consistent-type-assertions to assertionStyle "never"; disabling it requires an audited eslint-meta-allow-assertion-exemption marker.
eslint-override-paths-existno
Literal test-file paths in eslint.config.* overrides must exist on disk.
tsconfig-include-paths-existno
Literal tsconfig include/files entries must point at files that exist (globs exempt); checks this app and sibling apps.
eslint-plugin-contract-parityno
Every installed @boring-stack-pkg eslint plugin must appear in AGENT_CONTRACT.md, and vice versa.
Implementation lives under scripts/lint-meta/ in each template.
Adding a rule
Section titled “Adding a rule”- Implement
IMetaRuleunderscripts/lint-meta/rules/<category>/ - Register it in
scripts/lint-meta/registry.ts - Run
bun run generate:lint-meta-docs(ui) orbun run generate:lint-meta-docs(api) - Run the boringstack docs generators and commit the updated JSON catalogs
- Add a test under
tests/lint-meta/
Related
Section titled “Related”- Lint as the contract, ESLint plugin inventory
- Scripts & tooling, which script runs
lint:meta - Commands cheatsheet, day-to-day workflow commands