Skip to content
cccskills
FreePublished on GitHub

ayunis-core-backend

Ayunis Core backend specifics — module boundaries, key files, and project conventions. Complements the nestjs-hexagonal-backend skill.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34 stars(GitHub 34 + cccskills 0)

Run score Not tested

See how to install

Files included (1)

  • SKILL.md9.5 KB

SKILL.md (original)

Review the instructions the agent will receive before you install.

Ayunis Core Backend

Load the nestjs-hexagonal-backend skill first — this skill adds project-specific context on top.

Working Directory

All commands run from ayunis-core-backend/:

cd ayunis-core-backend

Module Boundaries

The backend enforces strict bounded contexts:

  • src/domain/* — Core business logic (agents, threads, messages, runs, models, tools, prompts, sources, RAG, etc.)
  • src/iam/* — Identity and access management (auth, users, orgs, subscriptions, quotas, teams, etc.)
  • src/common/* — Shared infrastructure only (base classes, utilities)
  • src/admin/* — Super admin routes

Cross-module communication uses exported use cases, not ports/adapters. When module A needs functionality from module B, module A imports B's module and injects B's use case directly — do NOT create a port in A with an adapter that wraps B. Ports (abstract interfaces) are only for infrastructure boundaries within a module (e.g., a repository port implemented by a persistence adapter).

The injection point must be module A's application layer (a use case or service) — never an infrastructure adapter. Infrastructure must not import use cases (enforced by the adapters-no-use-cases dependency-cruiser rule; inbound/driving adapters like queue consumers and scheduled tasks are exempt). If an adapter seems to need another module's functionality because the relevant data only appears mid-fetch, restructure so the adapter returns raw data and the application layer makes the cross-module call (e.g. the Cheerio URL adapter returns raw bytes, and RetrieveUrlUseCase decides whether to delegate PDFs to the file-retrieval use case).

Persistence records are an exception to the cross-module rule. TypeORM schema records (*.record.ts) may reference records from other modules via @ManyToOne / @OneToOne + @JoinColumn to declare foreign-key relationships — this is required for referential integrity (see the typeorm-migrations skill). The "don't cross modules" guidance applies to application-layer code (use cases, services), not to schema records, which are infrastructure that must mirror the DB.

Before modifying any module, read its SUMMARY.md. See ARCHITECTURE.md for the complete module index.

Place Responsibilities Before Writing Code

Before adding a backend file, inventory the target module's existing directories and inspect the two closest analogues. Follow the module's established vocabulary and placement; do not create a file directly under a layer root such as application/ when its role belongs in an existing subdirectory.

Assign each responsibility to one layer before implementation:

  • Presenter DTOs validate transport input completely, including conditional requirements and exact confirmation values.
  • Controllers translate validated HTTP input, invoke use cases, and translate results. They do not repeat DTO validation, make business decisions, or query repositories.
  • Application models hold data and behavior shared across commands or use cases without becoming domain state. Put them under application/models/.
  • Application services/use cases coordinate policy decisions and cross-module calls. Prefer a service when multiple entry points enforce the same policy.
  • Domain objects own business invariants intrinsic to persisted domain state.
  • Infrastructure records and mappers mirror persistence; they do not decide application policy.

Before finalizing the design, check for the same policy or entity lookup in adjacent controllers and use cases. Consolidate duplicated decisions at the narrowest shared application boundary and avoid loading the same entity twice in one request path.

ConfigService access — match the registerAs namespace

Backend config is loaded via registerAs('<namespace>', () => ({...})) factories. The lookup key must be prefixed with the namespace, or ConfigService.get(...) returns undefined and the call site silently uses a missing value (e.g. apiKey: undefined → 401 at runtime, not at boot).

For model providers, the namespace is models:

// CORRECT ✓
this.configService.get<string>('models.mistral.apiKey')
this.configService.get<string>('models.openai.apiKey')
this.configService.get<string>('models.anthropic.apiKey')

// WRONG ✗ — un-namespaced; returns undefined
this.configService.get<string>('mistral.apiKey')

Before adding a new provider handler, grep an existing one for the exact key path rather than transcribing from the .env variable name — the env-var → config-key mapping lives in the registerAs factory and the namespace is easy to drop on the floor.

User Context

User identity comes from ContextService, not method parameters:

// CORRECT ✓
const userId = this.contextService.get('userId');

// WRONG ✗
async execute(command: { userId: string })  // Don't pass context

Outbound Provider Errors

Any code path that calls an external provider over the network (LLM inference, embeddings, Mistral OCR, vendor SDKs) must classify failures through the shared taxonomy in src/common/errors (AYC-538) — do not invent per-adapter "service busy / timeout" error classes:

const providerError = wrapProviderFailure(error, { provider, modelId });
if (providerError) throw providerError; // ProviderUnavailableError family
// else: ApplicationError rethrows as-is; upstream 4xx and unknown errors
// stay in the module's own error family (potentially OUR bug — must remain
// a distinct, first-occurrence-alerting AppSignal incident)
  • wrapProviderFailure returns ProviderConnectionError (502) / ProviderTimeoutError (504) / ProviderServerError (502) for transport failures (errno, undici, TLS codes, SDK wrapper names — cause chains are walked) and upstream 5xx; undefined for everything else.
  • Grouping contract: each error sets name === code === PROVIDER_UNAVAILABLE_<CLASS>_<PROVIDER> so AppSignal opens one incident per provider+failure-class. There is only one reporting path — setError() is span.recordException(), and AppSignal's SpanProcessor forwards every exception span event from every span — and OpenTelemetry derives the grouping key exception.type as code ?? name (truthy code wins). Setting both to the same string is what makes the key stable no matter which span records it. Never add these errors to ignoreErrors in appsignal.cjs — per-occurrence notifications are disabled AppSignal-side so rate-based anomaly triggers keep working.
  • Suppressions are a registry, not a config literal: every ignoreErrors / ignoreRequestHook / disabled-instrumentation entry lives in SUPPRESSIONS in appsignal-hooks.cjs (AYC-563) and must carry a reason and an owning ticket. The spec asserts each ignoreErrors entry against a real instance of its error, because an entry naming a class whose instances carry a .code suppresses nothing at all — silently. appsignal.cjs only consumes the registry; it decides nothing.
  • Reference choke points: stream-inference.use-case.ts, get-inference.use-case.ts (models), embed-text.use-case.ts (embeddings), mistral-file-retriever.handler.ts (OCR, incl. ProviderRequestRejectedError for machine-generated 4xx — but not 401/403 auth-config bugs).
  • Not a provider: fetching customer-pasted URLs. Keep UrlRetrieverRetrievalError (422) and enrich its metadata with classifyTransportError() output instead.
  • This hand-written translate-catch at outbound choke points is the sanctioned exception to use-case-reference's "no hand-written try/catch error boundaries" rule.

Key Files

PurposeLocation
Architecture & module indexARCHITECTURE.md
Module summariessrc/[area]/[module]/SUMMARY.md
TypeORM configsrc/db/datasource.ts
OpenAPI spechttp://localhost:PORT/api/docs (when running)

[!IMPORTANT] Always update ARCHITECTURE.md and the module's SUMMARY.md file if necessary!

Running Tests

Run backend tests with pnpm test from ayunis-core-backend/ — never bare jest or npx jest.

cd ayunis-core-backend
pnpm test                      # full suite
pnpm test <path-or-pattern>    # a subset

pnpm test runs Jest through the project's configured runner (Node ESM flags, module-name mapping). Invoking jest / npx jest directly bypasses that setup, and the ESM-only p-queue dependency then fails to resolve — the embeddings-throttle and ingest-bulk-content RAG suites blow up with module-resolution errors.

Those failures are an artifact of the wrong invocation, not a real regression. Do not report them as "pre-existing failures on main" or hang caveats on a PR summary because of them — re-run with pnpm test and they pass. A suite that fails under pnpm test is a genuine signal worth investigating.

Runtime Health Check

When runtime validation is required by the repository's Proportional Workflow, verify the service responds before exercising the changed behavior:

curl http://localhost:PORT/api/health

The port depends on the dev slot — check with ./dev status from the repo root.

Reviews

No reviews yet. Tell others what you thought.

Comments

No login needed. Share how it worked for you, ask a question, or note where it did not run.

No comments yet. Be the first to write one.

Comments are published right away. Do not include personal information such as email addresses or phone numbers. Up to 2 URLs.

0 / 1,000 characters

This form is protected by reCAPTCHA, and the Google Privacy Policy and Terms of Service apply.

More skills from this repository

Query Ayunis deployed-environment telemetry in AppSignal — incidents, occurrence counts and error traces via the `appsignal-cli` binary; structured log attributes (user.id, org.id, nestjs.context, custom metadata) via the v2 logs API. Use when investigating a production/staging incident or log data — NOT for local dev logs (see backend-debugging).

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Frontend development in ayunis-core. Use when creating, modifying, or debugging frontend code (React, Feature-Sliced Design, API client).

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Debug backend runtime errors (500s, crashes, unexpected behavior). Use when something is broken at runtime — not for writing new code.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Shared test fixtures for backend specs — port mock factories, domain builders, and shared IDs in a per-module testing/ folder. Use when a module's *.use-case.spec.ts files repeat the same mock-repository literal, inline entity construction, or hardcoded UUIDs across files.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Analyze Cursor Bugbot PR comments through a root-cause lens: is the finding valid, and is the suggested fix the real fix or a bandaid over a deeper problem? Use when the user asks to check, assess, or triage bugbot comments.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

Self-review local changes before creating a PR. Use when the user says "review", "review my code", "check my changes", or wants confidence before submitting.

The English summary is being prepared. Showing the original description.

ayunis-core/ayunis-core34Updated Oct 26, 2026

See all skills by ayunis-core

Report a problem with this skill