Skip to content

Ninaku NestJS Stack Guide

Status: technology selection guide for ninaku-app-api.

This document complements the architecture, roadmap and API/schema discipline. It is informed by the NestJS official documentation and the ecosystem catalog at Awesome NestJS, but it deliberately does not treat every listed library as a recommendation.

The goal is to keep Ninaku boring, explicit, observable and maintainable while still using strong NestJS ecosystem tooling where it genuinely helps.

The current HTTP, pagination and observability contract is owned by FOUNDATION_HTTP_AND_OBSERVABILITY.md. It expands C into C1–C6 and defines measurable acceptance for D. These are implementation requirements, not a claim that C/D or their dependencies are already implemented.


1. Dependency policy

Ninaku follows this order of preference:

  1. Node.js / PostgreSQL native capability when it is simple and sufficient.
  2. Official NestJS package when Nest provides a maintained integration.
  3. Small, focused third-party package when it solves a clear gap and is actively maintained.
  4. Custom implementation only when Ninaku has domain-specific requirements that generic libraries cannot represent safely.

A package is not adopted because it appears in Awesome NestJS, has many stars, or reduces a few lines of code.

Before adding a runtime dependency, answer:

  • What real problem does it solve?
  • Can Nest/Node/PostgreSQL already solve it adequately?
  • Is the package maintained and compatible with our Nest/Node/ESM baseline?
  • Does it introduce framework lock-in or hide important behavior?
  • Does it preserve tenant/RLS/transaction/idempotency guarantees?
  • Can it be removed later without rewriting business modules?
  • What is the operational cost of running it?

Avoid "batteries included" boilerplates that impose a database model, generic CRUD architecture, generic repository pattern, microservices, GraphQL, Prisma migrations or authentication model that conflicts with Ninaku's existing contracts.


2. HTTP platform: keep Express

Decision: keep @nestjs/platform-express for the current implementation.

Reasons:

  • it is Nest's default adapter;
  • ecosystem compatibility is excellent;
  • there is no demonstrated HTTP-adapter bottleneck today;
  • the most important performance work for Ninaku is database/query design, pooling, caching where justified, payload design and horizontal scaling;
  • avoiding an unnecessary adapter migration keeps Phase 0 focused.

Rules to preserve portability and clean boundaries:

  • prefer normal Nest return values instead of direct Express.Response manipulation;
  • avoid @Res() unless streaming/file/protocol behavior truly requires native response access;
  • avoid business logic in Express middleware;
  • Domain/Application code must never import Express types;
  • Express-specific integrations stay at the Presentation/Foundation edge.

If future measurements show Express itself is a material bottleneck, adapter replacement can be evaluated with representative k6 scenarios instead of synthetic /health benchmarks.


3. Configuration: official @nestjs/config + Zod

Target: adopt early (Phase 0).

Packages:

text
@nestjs/config
zod                 # already present

NestJS 12 supports Standard Schema validation and recommends modern schema libraries such as Zod for new projects.

Use one typed configuration boundary and fail fast during startup.

Preferred direction:

ts
ConfigModule.forRoot({
  validationSchema: z.object({
    NODE_ENV: z.enum(['local', 'test', 'development', 'staging', 'production']),
    PORT: z.coerce.number().int().positive().default(3000),
    DATABASE_URL: z.string().min(1),
  }),
});

Rules:

  • do not read process.env throughout business modules;
  • configuration is grouped by technical concern;
  • secrets never receive default production values;
  • optional integration credentials are required only when that integration is enabled;
  • tests construct explicit configuration rather than depending on a developer machine.

Do not add another config library (nest-typed-config, configfy, etc.) unless the official module demonstrably cannot satisfy a requirement.

Implemented in Foundation: NODE_ENV, PORT, and discrete DATABASE_HOST/DATABASE_PORT/DATABASE_NAME/DATABASE_USER/DATABASE_PASSWORD/DATABASE_SSL variables instead of a single DATABASE_URL, because generated base64 passwords contain /, +, = characters that break a connection URL. ConfigModule.forRoot({ validate }) uses a Zod schema and fails fast naming the invalid keys, never their values.


4. Request validation: Nest 12 Standard Schema + Zod

Target: Foundation C3 (Phase 2).

NestJS 12 includes StandardSchemaValidationPipe, so Ninaku can use Zod without requiring a third-party Nest/Zod bridge for basic validation.

Preferred model:

text
HTTP input

Zod schema / DTO contract

StandardSchemaValidationPipe

Application command/query

Rules:

  • external input is validated at the boundary;
  • domain invariants are still enforced in Domain/Application and are not replaced by DTO validation;
  • parsing/coercion must be intentional;
  • IDs, money, dates, quantities and enums use explicit contracts;
  • validation schemas are not database entities.

C3 includes body, path, query and relevant headers, unknown-field policy, size/range limits and missing-versus-null semantics. The validation failure is the common C4 errors list; see the normative Foundation specification.

nestjs-zod appears in Awesome NestJS and may be useful if its OpenAPI/DTO ergonomics materially improve our implementation, but it is not a default dependency because Nest 12 already supports Standard Schema natively.


5. OpenAPI: official @nestjs/swagger

Target: Foundation C6, before business modules.

Package:

text
@nestjs/swagger

OpenAPI is part of the public API contract, not decoration added at the end.

Use it to document:

  • route purpose;
  • input/output DTOs;
  • pagination/cursors;
  • authentication requirements;
  • Problem Details responses;
  • idempotency/precondition headers where applicable;
  • versioned business operations.

Rules:

  • do not generate CRUD endpoints from entities merely to obtain Swagger;
  • C6 generates/validates OpenAPI in CI and detects incompatible changes;
  • HTTP tests prove the implemented response matches the contract and protocol exceptions;
  • the API contract must remain independent from persistence entities;
  • generated SDKs/mocks can be evaluated later from the OpenAPI artifact.

C1 defines natural requests, data with optional meta, headers and status codes. C2 defines meta.pageInfo, bounded lists and opaque/versioned/protected cursors. The owner of these Ninaku conventions is FOUNDATION_HTTP_AND_OBSERVABILITY.md.

Awesome NestJS lists automatic CRUD/OpenAPI frameworks. These conflict with Ninaku's task-oriented API rule and should not define our public API.


6. Error model: Ninaku Problem Details

Target: Foundation C4.

Ninaku already defines RFC 9457-style application/problem+json responses with stable extensions such as:

text
code
traceId
retryable
errors

errors, when present, is always a list using the shared location/field/code/message shape. Errors are not wrapped in data; the body's status agrees with HTTP. retryable alone never authorizes repeating a non-idempotent command. The current examples and acceptance tests live in the normative Foundation specification.

Awesome NestJS lists nest-problem-details, which is a useful reference and a candidate for evaluation, but Ninaku has domain-specific requirements around:

  • stable error codes;
  • safe PostgreSQL constraint translation;
  • tenant-safe 404 behavior;
  • idempotency conflicts;
  • retryability;
  • field validation errors;
  • trace correlation.

Therefore the default direction is a small Ninaku-owned exception/error mapping layer built on Nest exception filters and HttpAdapterHost, unless the third-party library proves it can express our contract cleanly without leaking its model into Domain/Application.

Domain/Application code returns typed failures and does not construct HTTP responses.


7. PostgreSQL access: pg first, typed query builder evaluated separately

Target: foundational decision before business modules.

The canonical PostgreSQL schema is authoritative. Application tooling must adapt to it, not regenerate it.

Baseline runtime candidate:

text
pg (node-postgres)

Why:

  • mature PostgreSQL driver;
  • explicit pool/client lifecycle;
  • complete SQL access;
  • works naturally with PostgreSQL functions, RLS and advanced SQL;
  • transactions are explicit and require the same checked-out client, which matches Ninaku's transaction-boundary design.

A higher-level typed query builder may be layered over pg, but it must not own migrations/schema evolution.

Kysely

Strong candidate to evaluate.

Useful because it is a type-safe SQL query builder rather than a schema-owning ORM. It can use the pg pool and keeps SQL concepts visible.

Potential fit:

text
canonical PostgreSQL
       ↓ introspection/codegen
TypeScript DB types

Kysely query builder

module persistence adapters

Drizzle

Candidate, but with stricter guardrails.

Drizzle supports introspecting an existing PostgreSQL database and using pg, but its migration/push tooling must not become an alternative schema authority.

For Ninaku, commands such as schema push/generate/migrate would be disabled from the normal application workflow unless explicitly approved as part of the canonical migration process.

Prisma / TypeORM

Not automatically rejected, but they are currently lower-priority candidates because Ninaku has:

  • a large existing PostgreSQL schema;
  • native functions/triggers/RLS;
  • explicit SQL migration contracts;
  • no desire for an ORM to regenerate the database model.

The final persistence choice must be proven with a real slice (organization or identity) including transactions, RLS context, functions and tests before becoming the platform standard.

Implemented in Foundation: src/foundation/database builds a pg.Pool from typed config, applying DATABASE_POOL_MAX as max, DATABASE_CONNECTION_TIMEOUT_MS as connectionTimeoutMillis, and DATABASE_STATEMENT_TIMEOUT_MS/DATABASE_LOCK_TIMEOUT_MS/DATABASE_IDLE_IN_TRANSACTION_TIMEOUT_MS as the matching pg session options (statement_timeout, lock_timeout, idle_in_transaction_session_timeout). The pool closes via its OnModuleDestroy hook, which app.close() always triggers; src/bootstrap/graceful-shutdown.ts calls app.close() on SIGTERM/SIGINT before draining telemetry, so no separate enableShutdownHooks() call is needed. registerGracefulShutdown returns that same memoised shutdown to src/main.ts, which invokes it when startup fails after the application was already built — app.listen() failing to bind the port is the realistic case. Without it the process would exit while a fully constructed application, and the pool it had already opened, were never closed. Returning the shutdown the signal listeners themselves use, rather than building a second one, is what makes this safe: a startup failure that races a SIGTERM still closes the pool exactly once, and the telemetry drain still runs even when app.close() throws, because the orchestrator drains it in a finally. Boot fails if the connected role is superuser or BYPASSRLS.


8. Request / tenant / transaction context

Ninaku needs per-request context for values such as:

text
traceId
principalId
organizationId
businessUnitId
legalEntityId
siteId
outletId
transaction client/context

Do not solve this by converting the whole dependency graph to Nest REQUEST scope.

Preferred options to evaluate:

  1. Node.js AsyncLocalStorage directly behind a Ninaku abstraction.
  2. nestjs-cls as a maintained Nest-friendly ALS wrapper.

nestjs-cls is listed by Awesome NestJS and is actively maintained. It can be useful, but Ninaku must still explicitly test context propagation across:

  • HTTP requests;
  • nested async calls;
  • database transactions;
  • queue workers;
  • scheduled jobs;
  • tests.

ALS/CLS is a transport mechanism for context, not authorization. Every protected operation still validates permissions and PostgreSQL RLS remains a security boundary.


9. Logging: Nest JSON behind a small Ninaku boundary

Status: implemented in Foundation C5 (src/foundation/logging; see FOUNDATION_HTTP_AND_OBSERVABILITY.md section 7.8). Nest's LoggerService contract is implemented directly by NinakuLoggerService instead of wrapping ConsoleLogger's json mode, because that mode does not produce the ISO timestamp or five-level severity the record shape requires; the same substitution point (app.useLogger()) stays available for nestjs-pino if that alternative is adopted later. Do not build a logger from scratch or expose a telemetry storage vendor to business modules.

The exact record and acceptance matrix live in FOUNDATION_HTTP_AND_OBSERVABILITY.md. Common fields are timestamp, level, event, service, serviceVersion and environment. Request, HTTP, module, operation, error and permitted organization fields are conditional on valid context.

Technical JSON uses trace_id/span_id for actual W3C/OpenTelemetry context; the public Problem Details extension remains traceId. Keep requestId, traceId and stable business commandId separate. Do not invent spans for startup logs or treat telemetry headers as trusted tenant identity.

Required policy:

  • one JSON record per line, bounded in size and structure;
  • allowlisted fields instead of complete requests, responses, users, headers or SQL parameters;
  • secret protection in error messages/causes, URLs, provider responses and captured/exported telemetry as well as ordinary records;
  • no passwords, PIN/OTP, access/refresh tokens, cookies, API secrets, connection strings, payment credentials, private certificate material or full sensitive payloads;
  • one responsible layer records an unexpected failure; no repeated stack at every layer;
  • expected validation/business rejection does not automatically generate urgent error alerts;
  • successful health checks are suppressed or sampled and production debug is off by default;
  • durable business/security audit evidence remains separate and is not sampled;
  • define retention, access, volume budget, bounded queues, export timeouts and shutdown/loss behavior;
  • prove staging lookup by response identifier and correlation to instrumented execution, not only console output.

nestjs-pino remains an alternative when compatible, tested and measurably useful. The same contract and security/performance tests apply; replacing the adapter must not rewrite business modules.


10. Health/readiness: official @nestjs/terminus

Target: adopt in Phase 0.

Package:

text
@nestjs/terminus

Expose separate concepts:

text
/health/live
/health/ready

live answers whether the process/event loop is alive enough to serve.

ready answers whether this instance should receive production traffic.

Readiness may include a lightweight PostgreSQL check and required runtime dependencies, but must not call every external provider on every probe.

Enable Nest shutdown hooks and support graceful shutdown/connection draining.


11. Rate limiting: Cloudflare first, @nestjs/throttler for application semantics

Target: adopt selectively.

Awesome NestJS lists the official @nestjs/throttler module.

Use two layers for different problems:

text
Cloudflare
  └─ coarse abuse / DDoS / edge rate limiting

Nest @nestjs/throttler
  └─ application-aware limits (login, OTP, sensitive operations)

Do not depend only on an in-memory throttler store once multiple replicas exist. Distributed limits require a shared store or edge enforcement.

Rate limits must account for trusted proxy headers correctly when running behind Cloudflare/Railway.


12. Caching: official @nestjs/cache-manager, but only by use case

Status: optional, not Phase 0 mandatory.

Package candidate:

text
@nestjs/cache-manager

Good candidates:

  • global/reference data;
  • public catalog projections;
  • expensive but reconstructable read models;
  • short-lived provider metadata.

Bad candidates:

  • current stock as source of truth;
  • payment state;
  • sale state;
  • accounting balances;
  • authorization truth without a robust invalidation contract.

Do not enable global response caching blindly on authenticated tenant APIs.

Redis/Valkey becomes a storage option only when multi-instance/shared cache requirements exist.


13. Redis: optional infrastructure, not a database replacement

Awesome NestJS lists multiple Redis integrations. We should not choose one until a real use case exists.

Potential uses:

  • shared cache;
  • distributed rate-limit state;
  • BullMQ;
  • ephemeral presence/realtime state;
  • explicitly designed distributed coordination.

Redis must not become the authoritative record for Sales, Inventory, Payments, Fiscal or Accounting.

If Redis is introduced, select one client/integration standard and centralize it in Foundation rather than allowing modules to instantiate clients independently.


14. Background jobs: official @nestjs/bullmq when durable async work is required

Status: adopt when the first durable asynchronous workflow exists.

Packages:

text
@nestjs/bullmq
bullmq

Good candidates:

  • fiscal/SRI delivery and reconciliation;
  • email/WhatsApp delivery;
  • provider webhooks;
  • document generation;
  • OTA synchronization;
  • analytics projection work;
  • retryable integration tasks.

Critical rule:

text
business transaction
    ├─ domain writes
    └─ outbox write
          ↓ commit
outbox dispatcher

BullMQ

idempotent worker

external provider

Do not COMMIT business state and then simply queue.add() hoping the process does not crash between the two operations.

Workers must be idempotent and observable.


15. In-process events: @nestjs/event-emitter only for non-durable notifications

Status: optional.

The official Nest event-emitter module is useful to decouple in-process reactions.

Use it only when losing the event on process crash is acceptable or when the durable fact is independently recoverable.

Do not use an in-memory event emitter as a replacement for:

  • outbox;
  • integration events;
  • financial/fiscal workflows;
  • reliable notifications;
  • cross-instance coordination.

Domain events may be dispatched in process after/around transaction orchestration, but durable external consequences require persisted evidence.


16. Scheduling: official @nestjs/schedule with replica-safe rules

Status: optional.

@nestjs/schedule is fine for simple local scheduling, but multiple application replicas can execute the same cron simultaneously.

Therefore a scheduled task must satisfy at least one of these:

  • duplicate execution is harmless and idempotent;
  • database claim/lease ensures only one worker performs the job;
  • it runs in a dedicated worker/scheduler instance;
  • the platform scheduler invokes a normal idempotent endpoint/job.

Do not use a process-local cron as the only guarantee for critical fiscal, payment or settlement work.


17. CQRS: do not adopt @nestjs/cqrs by default

Awesome NestJS lists the official @nestjs/cqrs and typed wrappers around it.

Ninaku already distinguishes commands, queries and domain ownership conceptually. We do not need a framework package merely to name classes Command and Query.

Start with simple application handlers/services and explicit module contracts.

Evaluate @nestjs/cqrs only when we have concrete value from:

  • standardized command/query buses across many modules;
  • decorators/pipelines around handlers;
  • sagas/process managers;
  • enough complexity that the bus reduces rather than increases indirection.

Avoid architecture ceremony without a use case.


18. Authorization: own Ninaku authorization model

Awesome NestJS lists generic RBAC libraries and external policy engines.

Ninaku already has a richer existing model:

text
principal
membership
role / permission / capability
Organization
Business Unit
Legal Entity
Brand
Site
Outlet
RLS

Generic role === admin packages must not replace this model.

Nest Guards/Decorators can provide the HTTP integration, but effective authorization is resolved through Ninaku Identity/Platform contracts and validated against the same scope model protected by PostgreSQL.

External engines such as Keycloak/OSO/Permit may be evaluated for specific future requirements, not adopted before proving they fit our scope/history/multi-business model.


19. Multi-tenancy: do not outsource the tenant model to a generic package

Awesome NestJS lists generic multitenancy packages such as nestjs-mtenant.

Ninaku's tenancy is already encoded in the domain and database contracts. It is not merely "choose database/schema by request".

Our runtime needs to propagate authorized context into PostgreSQL RLS and module contracts.

A generic multitenancy module may inspire implementation techniques, but it must not become the source of tenant ownership or bypass the existing Organization/Business Unit/Legal Entity model.


20. Testing: Vitest remains the standard

Current repository standard:

text
Vitest
Supertest
native PostgreSQL contract/integration tests

Keep this baseline.

Testing layers:

text
Domain unit tests
Application unit tests
Persistence integration tests
Module integration tests
HTTP e2e tests
Database native contract/concurrency tests
Load/resilience tests (k6)

@golevelup/ts-vitest is listed by Awesome NestJS and can be evaluated when it materially simplifies typed mocks. Do not add mocking libraries merely to make every dependency a mock; integration tests against real PostgreSQL are essential for RLS, constraints, functions, locking and transaction behavior.

OpenAPI-driven mocks/Pact-style testing may become useful for external consumers/providers later. C6's own OpenAPI generation, schema validation, runtime-contract tests and incompatible-change detection are required in Foundation, not deferred to that future evaluation.


21. Observability: OpenTelemetry and W3C Trace Context

Decision for Foundation C5: OpenTelemetry with W3C Trace Context and configurable export. The instrumentation strategy is selected; compatible packages, storage destination and operational limits still require implementation evidence. Approving this document does not provision a provider.

Status: bootstrap implemented in Foundation C5 (src/foundation/telemetry; see FOUNDATION_HTTP_AND_OBSERVABILITY.md section 7.8) with @opentelemetry/instrumentation-http/-pg, a redacting exporter wrapper, and bounded batching/shutdown. The exporter stays disabled until OTEL_EXPORTER_OTLP_ENDPOINT is configured; the storage destination, sampling policy and retention/access/cost budget remain pending owner confirmation.

Keep technical logs, traces, metrics and durable audit separate. Do not run overlapping APM SDKs or put runtime HTTP logs into the transactional database.

The previously evaluated @nestjs/observe path is not a second mandatory SDK. An alternative must preserve the agreed contract and pass compatibility, security, exportability and performance review before replacing the selected strategy.

C5 must demonstrate:

  • useful HTTP/database spans, not only a traceId field;
  • request/log/trace correlation without trusting trace headers for authorization;
  • data minimization and protection at capture and export;
  • bounded attribute cardinality, queues, retries, timeouts and shutdown;
  • documented sampling, access, retention and volume/cost policy;
  • staging lookup of a response identifier and its instrumented execution;
  • exporter-failure behavior without unbounded API memory growth.

The exact policy and acceptance tests are in FOUNDATION_HTTP_AND_OBSERVABILITY.md. Extend the same conventions to jobs/integrations as those paths are implemented. A Collector is an optional deployment choice, not automatically required additional infrastructure.


22. Security/HTTP hardening

Use Nest/Express ecosystem primitives deliberately:

  • Helmet/security headers where appropriate;
  • strict CORS allowlists per environment;
  • body-size limits;
  • secure cookie configuration if cookies are used;
  • trusted-proxy configuration for Cloudflare/Railway;
  • request timeouts and upstream timeouts;
  • endpoint-specific rate limiting;
  • redacted logs;
  • validation before application handling.

Security-critical behavior must be explicit in bootstrap/Foundation and covered by e2e tests.


CapabilityPreferred choiceStatus
HTTP adapter@nestjs/platform-expressKeep
Config@nestjs/config + ZodAdopt Phase 0
Input validationNest StandardSchemaValidationPipe + ZodFoundation C3
OpenAPI@nestjs/swaggerFoundation C6 with CI verification
Health/readiness@nestjs/terminusAdopt Phase 0
PostgreSQL driverpgStrong baseline candidate
Typed SQLKyselyEvaluate
ORM alternativeDrizzleEvaluate with strict schema guardrails
Request contextnative AsyncLocalStorageFoundation B
LoggingNest JSON behind a small Ninaku boundaryFoundation C5
Pinonestjs-pinoAlternative if justified; same contract/tests
Rate limitingCloudflare + @nestjs/throttlerAdopt selectively
Cache@nestjs/cache-managerUse case driven
Redischosen client/integration laterNot mandatory
Durable jobs@nestjs/bullmq + BullMQAdopt when required
Local events@nestjs/event-emitterOptional / non-durable only
Scheduling@nestjs/scheduleOptional / replica-safe only
CQRS package@nestjs/cqrsDo not adopt yet
Generic RBACthird-party RBAC packageDo not adopt by default
Generic multitenancythird-party tenant packageDo not adopt as domain model
Unit/e2e testingVitest + SupertestKeep
Test mock helpers@golevelup/ts-vitestOptional
Tracing/APMOpenTelemetry + W3C Trace ContextFoundation C5; implementation/backend to verify

24. Packages/styles we intentionally avoid by default

Do not introduce these patterns without an explicit architectural review:

  • generic CRUD generators/controllers;
  • generic repository abstractions spanning unrelated domains;
  • schema-first ORM migration ownership over canonical SQL;
  • automatic GraphQL layer just because a boilerplate includes it;
  • microservice transports inside the monolith;
  • event sourcing for ordinary CRUD/business state;
  • CQRS buses everywhere without a use case;
  • Redis-backed locks as the first answer to PostgreSQL concurrency problems;
  • request-scoped providers throughout the application;
  • global cache on authenticated operational endpoints;
  • process-local cron as a uniqueness guarantee;
  • role-only authorization that ignores business scope;
  • boilerplates that replace Ninaku's module/domain boundaries;
  • ternary expressions in TypeScript source, enforced by npm run lint:no-ternary.

25. Phase 0 technology spike

Before the first business module, prove a thin vertical technical slice:

text
Express HTTP

typed config

request/trace context

Zod input validation

PostgreSQL runtime pool (`ninaku_runtime`)

transaction + SET LOCAL tenant/RLS context

health/readiness

structured logs/traces

OpenAPI

The spike should include:

  • one public health endpoint;
  • one protected test endpoint;
  • one RLS-protected PostgreSQL read;
  • one transactional PostgreSQL write in a disposable test database;
  • a denied cross-tenant access test;
  • graceful shutdown test/manual verification;
  • configuration failure test;
  • trace/request ID visible in logs/errors;
  • basic k6 smoke baseline.

Once this works cleanly, the same foundation is reused by real modules instead of discovering infrastructure rules while building Sales/Auth.

Issue #13 now tracks four main slices A–D, with C delivered as C1–C6 rather than exactly four PRs overall. FOUNDATION_HTTP_AND_OBSERVABILITY.md is the owner of the expanded scope and acceptance matrix. Required additions to the spike are:

  • success/error/collection contract tests, including protocol exceptions;
  • cursor integrity/scope/authorization tests and explicit consistency;
  • secret-leak tests against actual serialized/exported output;
  • concurrent request/tenant/trace isolation;
  • staging log/trace lookup using the response identifier;
  • bounded telemetry export failure and shutdown behavior;
  • OpenAPI generation/validation, runtime agreement and incompatible-change checks;
  • a reproducible D report measuring available paths and instrumentation cost, without extrapolating health-check results to business capacity.

Decisions on cursor protection/expiry and telemetry backend, access, retention and budgets must be resolved in C2/C5. Describing these outcomes does not mark implementation complete or enable production; go-live remains issue #7.


26. Guiding principle

Use the NestJS ecosystem to remove accidental complexity, not to outsource Ninaku's architecture.

A good dependency should make one technical concern easier while leaving Domain/Application ownership more explicit.

If adding a package makes it harder to answer "who owns this rule, transaction or business fact?", it is probably the wrong abstraction for Ninaku.

Application Foundation in progress. Tracked in issue #13.