Quality Guardrails
Guide 39
Ndigo ships guardrails at three levels: lint rules that catch wrong framework usage, an inspection manifest that answers “what does this app expose?”, and the validation lanes that keep the repository itself honest. This guide is the map to those checks and how to wire them into an app.
Audience: Application developers and framework contributors · Prerequisites: Workspace bootstrapped; guide 01 or 02 · Reference app: apps/05-ndigo-api
What you’ll learn
Section titled “What you’ll learn”- Enable the consumer lint rules and know what each one guards.
- Tell the consumer rule set from the repo-only internal rules.
- Generate and consume the application inspection manifest.
- Run the same validation lanes CI runs.
- Add a package surface without breaking the manifest/API gates.
1. Two rule sets
Section titled “1. Two rule sets”| Rule set | Package | Scope |
|---|---|---|
| Consumer guards | @ndigo/oxlint-plugin |
Framework API misuse in any app |
| Repo-only guards | root src/oxlint/ (internal/*) |
This monorepo’s layout and house style |
The split is deliberate: a rule belongs to the consumer plugin only if
it guards use of the framework API. Rules keyed to packages/ndigo-*, the
internal-file convention, doc headers, or repository naming stay internal.
2. Enabling the consumer rules
Section titled “2. Enabling the consumer rules”{ "jsPlugins": ["@ndigo/oxlint-plugin"], "rules": { "@ndigo/no-cross-paradigm-decorators": "error", "@ndigo/no-scheduled-in-browser": "error", "@ndigo/no-signals-in-entity": "error", "@ndigo/no-string-registry-lookup": "error", "@ndigo/no-unsafe-view-name-literal": "error", "@ndigo/no-duplicate-mapping-path": "error", },}| Rule | Catches |
|---|---|
no-cross-paradigm-decorators |
Decorator families mixed outside their paradigm |
no-scheduled-in-browser |
@ndigo/scheduler imports in browser-platform files |
no-signals-in-entity |
@ndigo/signals reactivity leaking into ORM entities |
no-string-registry-lookup |
String-literal registry lookups where a token is available |
no-unsafe-view-name-literal |
Absolute/../NUL view names; invalid island tags |
no-duplicate-mapping-path |
Duplicate same-method mappings in one class |
The duplicate-path rule is class-local and syntactic: it compares mapping decorators attached to methods of one class, groups by normalized path and resolved method identity, and skips mappings with extra conditions. It is best-effort by design; do not rely on it to see base-class or dynamically built paths.
3. The repo-only rules
Section titled “3. The repo-only rules”The internal plugin runs from TypeScript with no build step and is loaded by the
root .oxlintrc.json. It enforces:
internal/no-direct-integration-imports— no linkedom outside@ndigo/dom.internal/no-internal-file-imports/-exports—_*.tsstays private.internal/no-uppercase-regexp-constants— regex constants use the agreed form.internal/no-abstract-class-without-prefix—Abstract*naming.internal/no-mixed-case-acronyms— acronym casing.internal/requires-doc-header— the module summary block.internal/no-global-state-in-create-factories—create*stays global-free.
Run them with the same command as the consumer rules: pnpm lint.
4. The inspection manifest
Section titled “4. The inspection manifest”@ndigo/inspect reads the live application and emits a deterministic JSON
description of routes, handler mappings, and declared configuration keys. It
never reads or decrypts values — only presence and the winning source — so the
manifest is safe to print and diff.
ndigo inspect ./[apps/05-ndigo-api](https://github.com/ndigo/ndigo/tree/main/apps/05-ndigo-api)ndigo routes ./[apps/05-ndigo-api](https://github.com/ndigo/ndigo/tree/main/apps/05-ndigo-api) # aliasimport { inspectApplication } from '@ndigo/inspect';
const manifest = inspectApplication({ context, routes, mappings: context.getValue(DISPATCHER), configurationClasses: [ServerConfig], now: '2026-01-01T00:00:00.000Z', // optional: byte-identical snapshots});Manifest shape: version, app, routes, mappings, config. Everything is
sorted and de-duplicated; generatedAt is the only run-to-run difference unless
you inject now. Use it for:
- a route/mapping inventory in CI or release notes;
- drift checks against a committed manifest;
- answering “which config keys does this app declare?” without reading secrets.
5. The validation lanes
Section titled “5. The validation lanes”The same commands run locally and in CI:
pnpm build # tsc -b per packagepnpm typecheck # per-project build + spec lanepnpm lint # oxlint, zero warningspnpm test # unit + integrationpnpm format:check # oxfmtpnpm docs:links # relative links and anchorspnpm guides:check # guide contractpnpm doc:coverage # TSDoc depth gatepnpm doc:api # TypeDoc, zero warningspnpm manifest:check # package/README/metadata conformancepnpm agents:check # generated agent filespnpm doc:api writes the per-package api/ trees; CI compares them and fails
on drift. pnpm manifest:check enforces the README skeleton and package
metadata. pnpm doc:coverage is the zero-tolerance member-docs gate.
6. Adding a package or public symbol
Section titled “6. Adding a package or public symbol”- Scaffold with the generators (
ndigo generate/ the package generator). - Fill the README skeleton and manifest fields.
- Write TSDoc on every public symbol and member (
@param/@returns). - Run
pnpm doc:apiand commit the generated tree. - Add the package to the API index and, if it has a guide, to the guide index.
- Run the validation lanes;
manifest:checkand the docs freshness diff are the usual failures.
7. Pitfalls
Section titled “7. Pitfalls”- Enabling a consumer rule as
warndoes not fail CI. The lane runs with--deny-warnings; useerror. - The internal rules are not published. Do not reference
internal/*from an app’s config. - The manifest is not a config dump. Values are deliberately absent; do not add a “show values” switch.
- Duplicate-path lint is class-local. A duplicate across base classes or dynamic paths still reaches the dispatcher.
- Generated docs are not hand-edited. Regenerate after API changes.
See also
Section titled “See also”- Guide 37 — The
ndigoCLI — command surface - Guide 90 — Package Documentation — README/API standard
@ndigo/oxlint-plugin— the consumer rules@ndigo/inspect— the manifest- — the rule-surface split