Convention Checks Reference
69 automated checks (C1--C143) enforced across all Mantle projects. Grouped by category. For detection patterns and exact fixes, see the agent-enforcement retrieval system (~/Repositories/agent-enforcement/rules/mantle/).
Module and Compiler (C1--C3)
| Check | Rule |
|---|---|
| C1 | ESM + strict TypeScript. "type": "module", strict: true, verbatimModuleSyntax, noUncheckedIndexedAccess, isolatedDeclarations. No require(). |
| C2 | Stage 3 decorators only. Never set experimentalDecorators: true. |
| C3 | ES2025 target + Node.js 24. Lambda runtime: nodejs24.x. |
Imports and Dependencies (C4--C6)
| Check | Rule |
|---|---|
| C4 | Import z from @j0nathan-ll0yd/validation, never zod directly. Use z.toJSONSchema(), never zod-to-json-schema. |
| C5 | All HTTP requests use fetchWithTimeout from @j0nathan-ll0yd/resilience. No raw fetch. |
| C6 | Use getRequiredEnv/getOptionalEnv from @j0nathan-ll0yd/env. No raw process.env. Never access env vars at module level. |
Handler Patterns (C7--C9)
| Check | Rule |
|---|---|
| C7 | All handlers use define*Handler factories. No class-based handlers. No middy. |
| C8 | Observability via define*Handler (calls withObservability internally). Explicit wrapping only for non-factory handlers. |
| C9 | API responses use buildValidatedResponse. Errors use buildErrorResponse. |
Database (C10--C11)
| Check | Rule |
|---|---|
| C10 | Entity queries: static class, @RequiresTable decorator, withQueryMetrics wrapping, exported as bound functions. |
| C11 | All delete() and update() must have .where(). Enforced by Drizzle ESLint rules. |
Error Handling (C12)
| Check | Rule |
|---|---|
| C12 | CustomLambdaError subclasses for HTTP errors. ok()/err() Result type for service-layer expected failures. Never throw for expected paths. |
Testing (C13--C15)
| Check | Rule |
|---|---|
| C13 | All tests in test/ mirroring src/. Integration tests use .integration.test.ts suffix. |
| C14 | Each code path tested at exactly one level -- unit OR integration, not both. |
| C15 | Vitest 4.0+ with @vitest/coverage-v8. Never Jest. |
Formatting and Style (C16--C17)
| Check | Rule |
|---|---|
| C16 | dprint: lineWidth: 157, indentWidth: 2, semiColons: asi, trailingCommas: never, quoteStyle: preferSingle. Never Prettier. |
| C17 | Conventional commits (type(scope): description). No AI attribution lines. |
Infrastructure (C18--C20)
| Check | Rule |
|---|---|
| C18 | HCL identifiers use snake_case. AWS name attributes use ${name_prefix}-PascalCase. Files: lambda_{snake_case}.tf. |
| C19 | One .tf file per Lambda. Each is a module block sourcing mantle/modules/lambda. |
| C20 | All Lambdas use arm64 + nodejs24.x. Exception: Lambda@Edge uses x86_64. |
Infrastructure Generation (C21--C28)
| Check | Rule |
|---|---|
| C21 | mantle generate infra is the sole source for .tf files. Never hand-write what the CLI can generate. |
| C22 | Ejected .tf files (no header comment) must be documented in the project plan. |
| C23 | Use module outputs (module.x.output), never raw resource references. |
| C24 | Env var names in .tf must match getRequiredEnv() calls in source. |
| C25 | Build output directory names must match function_name in .tf files. |
| C26 | Resource relationships use references, never hardcoded literals. Verify attributes exist in provider docs. |
| C27 | No deferred infrastructure. No TODOs in .tf files. Implement resources now. |
| C28 | Lambda@Edge: no @j0nathan-ll0yd/* imports (except observability/edge-logging), no layers, no define*Handler. |
Database Deployment (C29--C32)
| Check | Rule |
|---|---|
| C29 | DSQL deployment requires 3 steps: tofu apply, mantle db migrate, mantle db apply-permissions. |
| C30 | @RequiresTable with Insert or Update must also include Select (Drizzle RETURNING requires it). |
| C31 | Never use admin DSQL role to fix permission errors. Only MigrateDSQL may use admin. |
| C32 | Framework packages that query tables must export permission requirements (e.g. AUTH_TABLE_PERMISSIONS). |
Package Scripts (C33)
| Check | Rule |
|---|---|
| C33 | package.json scripts delegate to mantle CLI where equivalent functionality exists. Always specify --stage in deploy scripts. |
EventBridge (C34)
| Check | Rule |
|---|---|
| C34 | Prefer emitEvent/emitEvents from @j0nathan-ll0yd/core. Raw putEvents only when full control is needed. |
Validation and Logging (C35--C37)
| Check | Rule |
|---|---|
| C35 | API handlers use schema/querySchema on defineApiHandler. No raw event.body parsing. |
| C36 | Use logger from @j0nathan-ll0yd/observability. No console.log anywhere. Lambda@Edge uses edge-logging. |
| C37 | Handler files should be thin orchestrators. Files over 150 lines warrant extraction to src/services/. |
Framework Imports (C38)
| Check | Rule |
|---|---|
| C38 | Only import from declared @j0nathan-ll0yd/* exports. Allowed subpaths: auth/schema, aws/cloudfront-keyvaluestore, cli/config, database/orm, database/migrate, mcp/tools, observability/edge, testing/*. |
Database OCC Retry (C39)
| Check | Rule |
|---|---|
| C39 | withQueryMetrics includes automatic OCC retry. Do not add manual retry around it. Opt out with { retry: false }. |
Validation Result (C40)
| Check | Rule |
|---|---|
| C40 | validateSchema() returns an object. Check .success, never truthiness. Use .data instead of re-parsing. |
Resource Naming (C41)
| Check | Rule |
|---|---|
| C41 | name_prefix is environment only. Globally unique resources (S3) include project name. Account-scoped resources use ${name_prefix}-PascalCase. |
IAM Policy Generation (C42)
| Check | Rule |
|---|---|
| C42 | New services in service-extractor.ts must use import-based detection (Group B pattern), not env-var gating. |
Integration Testing (C43--C49)
| Check | Rule |
|---|---|
| C43 | Test cleanup: truncateAllTables() in afterAll, never dropAllTables(). |
| C44 | Integration test configs import TIMEOUTS from @j0nathan-ll0yd/testing/integration. No hardcoded timeouts. |
| C45 | Shared test constants (e.g. MAX_WORKERS) must be identical across files. |
| C46 | CI uses docker compose up -d --wait. No manual polling loops. |
| C47 | CI PostgreSQL: fsync=off, synchronous_commit=off, tmpfs for data dir. |
| C48 | Test infrastructure errors must include actionable diagnostics (available schemas, tables, connection state). |
| C49 | Sequential DDL in globalSetup. No Promise.all for schema/table creation. |
Dependency Ownership (C50--C52)
| Check | Rule |
|---|---|
| C50 | Instance dependencies must not declare packages owned by framework (e.g. better-auth, @opentelemetry/api). |
| C51 | All repos use createMantleEslintConfig() from @j0nathan-ll0yd/eslint-config. |
| C52 | Shared dependency upgrades start in the Mantle pnpm catalog, not in instances. |
OpenSpec Drift Tether (C128--C130)
| Check | Rule |
|---|---|
| C128 | Behavioral specs are tethered to tests: line-leading // covers: <capability>#<Requirement Name> in the covering test; tethered requirements read "SHALL ..., verified by <test file:line>"; every spec Purpose states it is only as true as its covering tests. Enforced by mantle check openspec (blocking in framework CI, advisory at instances). |
| C129 | Scope-match: a requirement may claim only what its covering test actually asserts. Broader behavior belongs in untethered requirements or prose. Multi-test requirements scope-label each verified by citation. |
| C130 | Specs are behavior-only: cite the owning schema (file:line) and defer to it -- never restate Zod/OpenAPI/TypeScript field shapes. Enforced by mantle check openspec + each instance's check-openspec-behavior-only.sh fence. Validate format with openspec validate --all --strict (pinned @fission-ai/openspec@1.10.0, telemetry off). |