Working with AI Assistants
:::info Source
Sourced from the repo-root AGENTS.md.
:::
Authoritative operating manual for all AI assistants (Claude Code, Cursor, Copilot, Aider, etc.) working in this repo. All rules here are non-negotiable unless a spec document explicitly supersedes them. On conflict: spec docs in
https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTechwin. This file is a distillation; specs are source of truth.
0. Repository Orientation
- Name: Ghasi-edTech
- Shape: Turborepo monorepo, pnpm workspaces. Root
package.jsonmust declare"workspaces": ["services/*", "packages/*"], andpnpm-workspace.yamlmust list the same globs in the same order (platform convention; keep them in sync). The Docusaurus portal lives inghasi-e-documentation/docs-portal/, not underapps/here. - Languages: TypeScript 5.6+ (primary), Python 3.11+ (ML/data only), Go 1.21+ (optional perf-critical)
- Domain: Enterprise multi-tenant ed-tech platform (LMS runtime, authoring, marketplace, AI tutoring, offline-first)
- Specs root:
https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech— numbered specs,docs,docs\roadmap\, anddocs\standards\live under...\docs\in that repo (not in this monorepo). - Product backlog (epics & user stories — documentation repo):
https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech—EPICS.md(frontend/product-area epics),USER_STORIES.md(detailed stories,US-*IDs), optional per-epic filesEP-*.md, plus supporting files (FRONTEND_ROADMAP.md,jira-import.csv). Use this folder in addition todocs/07-epics-and-user-stories.mdwhen resolving an epic/story by title, EPIC-* id, US-* id, or when the story is not listed under07-epics-and-user-stories.md. - Epic ID alignment (Jira vs backlog): In
backlog/EPICS.md, epics use ids likeEPIC-SYN-001(product-area prefix + sequence). Jira may use a different epic key orEP-xxstyle number — those numbers do not have to matchdocs/07-epics-and-user-stories.mdor each other. When a user citesEP-xxor a Jira epic name, searchbacklog/EPICS.mdandUSER_STORIES.mdby epic title or byEpic ID:/EPIC-*; do not reject a match solely becauseEP-xx≠ backlog numbering.- Per-service specs (17 markdown files) live under
https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech<name>\— not underservices/<name>/in this code repo. The/scaffold-servicecommand generates code only and verifies those files exist; it does not create or duplicate specs here. - If the folder layout is renamed (e.g. to
specs/), update every reference in.cursor/rules/*.mdc,CLAUDE.md, and this file.
- Per-service specs (17 markdown files) live under
Version control: AI assistant files (Cursor, Claude Code, GitHub Copilot)
- Commit (so every machine and teammate gets the same behavior): root
commands/,.cursor/rules/,.cursor/workflows/,.claude/rules/,.claude/commands/,.github/rules/,.github/commands/,.github/copilot-instruction.md(and any other Copilot instruction files you add under.github/), plusAGENTS.md,CLAUDE.md, and.cursorruleswhen they change. - Commit (generated platform artifacts that must stay in lock-step with source):
services/<name>/openapi.json(per-service Swagger),postman/(collections, environments with blank + disabled secret values, test-data stubs, scripts, README),packages/sdk-client/(TypeScript client generated from OpenAPI). The Docusaurus developer portal lives in theghasi-e-documentationrepo underdocs-portal/; regenerate it there withpnpm regenafter/sync-api-surface(seecommands/sync-docs-portal.md). Re-running generators on a clean tree must produce zero diff where applicable. - Do not commit ephemeral pipeline output:
.claude/artifacts/(session-local working files from playbooks such as/dev-pipeline). It is listed in.gitignoreand.cursorignore. - Do not commit machine-local Claude Code settings:
.claude/settings.local.json(also gitignored). - Do not commit real secrets in
postman/environments/*.postman_environment.json. Production environment values stay blank withenabled: false. Real credentials live in 1Password / KMS only.
Canonical spec files (read when relevant)
| Topic | File |
|---|---|
| Enterprise architecture | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| DDD + bounded contexts | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Microservice list | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Event-driven architecture | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| API design | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Data models | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Security + tenancy | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Observability | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Testing | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Frontend design | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Frontend workflows | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Authoring tool | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| LMS runtime player | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech |
| Multi-platform frontend (implementation) | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech — mandatory before any work under apps/web-application, apps/mobile-shell, apps/desktop-shell, or UI surfaces that implement web/mobile/desktop shells. Read at minimum the platform file: docs/frontend/web/04-web-app-specification.md, docs/frontend/mobile/05-mobile-app-specification.md, or docs/frontend/desktop/06-desktop-app-specification.md when touching that surface; read docs/frontend/common/02-architecture-overview.md when architecture or cross-cutting client concerns change. This repo’s docs/FRONTEND_SPECS.md summarizes paths and scope. |
| Frontend/product epics & stories (backlog folder) | https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech, ...\backlog\USER_STORIES.md, and ...\backlog\EP-*.md when present — search here for epic title and Epic ID: (e.g. EPIC-SYN-001 Offline Sync, Outbox & Conflict Resolution). Jira epic keys / EP-xx labels may differ; do not require numeric parity with 07-epics. |
Supporting standards (compact, for day-to-day decisions)
https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech— required files + directory skeleton for every new servicehttps://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech— one-page naming authorityhttps://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech— canonical error codeshttps://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech— merge checklist
ghasi-Multi-agent workflows, user stories, and spec access (mandatory)
- Documentation repo path:
D:\GhasiTech\ghasi-e-documentation\Ghasi-edTech\— add theghasi-e-documentationrepo as a workspace folder in the IDE when working from Cursor so paths resolve. The platform slug (e.g.ghasi-edtechin JSON) maps to theGhasi-edTechdirectory name under that repo. - If specs cannot be loaded (file not found, repo not attached, permission error): halt and ask the user. Do not implement from summaries, prior chat, or ghasi-Multi-agent artifacts alone; do not silently deviate from the documented API/event/data contracts.
- User stories must be traced to
docs/07-epics-and-user-stories.mdand/orbacklog/EPICS.md+backlog/USER_STORIES.md(and any matchingbacklog/EP-*.md) in the documentation repo, plus a user-supplied path when given, and toservices/<name>/specs for each owning/collaborator service. Before concluding that an epic/story id does not exist, search thebacklog/folder (by id, title, or keyword). Stories that span multiple services require work per bounded context named in the story or an explicit written scope decision from the user. - Acceptance criteria, preconditions, postconditions, and story-level definition of done in the documentation are binding. Gaps or contradictions require clarification before coding — not silent deferral.
- ghasi-Multi-agent workflow (v3.2): create a feature branch before implementation; if clarifications are needed, emit
clarifications-needed.mdand run the Clarification Agent (clarifications-resolved.md) — do not end the workflow; resume from spec-review through PR after answers. Execution is complete only when all in-scope ACs and DoD are green (roll-up inworkflow-state.md:implementation_overall/execution_complete) unless each deferred AC has a written Jira key inworkflow-state.md(ac_deferrals_to_jira). Cross-service stories must cover every named collaborator. At PR / handoff, updateworkflow-state.mdwith that roll-up. You MAY write a localimplementation-status.md(full AC table) for your workspace — it is gitignored and must not be committed; the committed source of truth for status isworkflow-state.md.
See also: .claude/commands/ghasi-Multi-agent.md, .cursor/workflows/ghasi-Multi-agent.yaml (version 3.2).
Why docs/frontend/** was sometimes skipped (root cause)
- Not in the original “canonical spec files” table — agents were directed to
docs/08-…–docs/11-…anddocs/standards/but the nesteddocs/frontend/tree was not listed, so implementers defaulted to parent guidelines only. - ghasi-Multi-agent
load-contextdid not enumeratedocs/frontend/**— the workflow loadeddocs/07-…,docs/standards/…, andservices/<name>/but did not require the frontend subtree; orchestrators could pass spec-review without reading platform-specific 04/05/06. CLAUDE.md“do not pre-load specs” — appropriate for token use, but without an explicit exception fordocs/frontendon frontend tasks, agents skipped the implementation specs.- Second workspace not attached — if
ghasi-e-documentationis not added as a Cursor workspace folder, absolute paths fail; agents must halt (see Hard stop in ghasi-Multi-agent docs) rather than guess. .cursor/rules/90-frontend.mdcreferenced08–11paths in the header but notdocs/frontend/README.md— now aligned (see that file).
1. Tech Stack Mandates
| Concern | Choice | Forbidden |
|---|---|---|
| Backend framework | NestJS 11 (TS 5.6+ strict) | Raw Express for services, Fastify, Koa |
| ORM | Drizzle ORM (chosen in service specs; authoring/assignment/delivery specs all use Drizzle + drizzle-kit) | TypeORM, Sequelize, raw SQL strings in app code |
| Migrations | drizzle-kit (generate + push); plain SQL migrations committed to src/infrastructure/migrations/ | Ad-hoc schema drift, hand-applied SQL |
| DB | PostgreSQL 16 (per-service schema, RLS mandatory) | Cross-service DB reads, shared tables |
| Cache | Redis 7 | In-memory maps for multi-instance state |
| Message broker | NATS JetStream | RabbitMQ, plain Kafka, polling |
| Search | OpenSearch (lexical) + pgvector (semantic) | Elastic cloud, managed Pinecone |
| Validation | Zod (TS), Pydantic (Py), Ajv for JSON Schema | Hand-rolled validators, any casts |
| ID generation | ULID (prefer) or UUIDv7 — branded types | Autoincrement, plain UUID, sequences |
| Frontend (web) | Next.js 14+ App Router, TailwindCSS, Radix UI, Lucide icons | Recharts (a11y gaps), Redux global store, MUI |
| Client state | TanStack Query + Zustand | Redux, MobX |
| Forms | React Hook Form + Zod (shared with backend DTOs) | Formik, hand-rolled |
| Animation | Framer Motion (gated by prefers-reduced-motion) | CSS-in-JS runtime libs for animation |
| i18n | ICU MessageFormat | Hardcoded strings, concatenation |
| Charts | Visx | Recharts |
| Offline DB | Dexie (web), SQLite (native) | localStorage for structured data |
| Observability | OpenTelemetry SDK via @ghasi/telemetry wrapper | Vendor SDKs directly (Datadog, NewRelic) |
| LLM clients | Only ai-gateway-service imports provider SDKs | openai, anthropic, etc. anywhere else |
| Testing | Vitest (unit/integration), Playwright (E2E), Pact (contract), k6 (load), Stryker (mutation) | Jest in new services, Cypress |
2. Monorepo Layout (Turborepo)
.
├── AGENTS.md # this file
├── CLAUDE.md # Claude Code entry (imports AGENTS.md)
├── .cursorrules # legacy Cursor entry
├── .cursor/rules/*.mdc # Cursor MDC rule packs (auto-attached by glob)
├── turbo.json # Turborepo pipeline
├── package.json # root manifest: `workspaces` + `packageManager: pnpm@…`
├── pnpm-workspace.yaml # same globs as `package.json#workspaces` (required)
├── tsconfig.base.json # shared strict TS config
├── services/ # 19 NestJS microservices (one bounded context each) — **code only**
│ └── <service-name>/ # 17 spec markdowns live in doc repo: …\ghasi-e-documentation\Ghasi-edTech\services\<service-name>\
├── apps/ # optional stub only — developer portal is in ghasi-e-documentation/docs-portal/
│ └── README.md # points to the documentation repo
│ # (planned) web-learner, web-authoring, web-admin, mobile, desktop live here when scaffolded
├── packages/
│ ├── domain-primitives/ # @ghasi/domain-primitives — branded IDs, shared VOs (TenantId, ULID, ISODate, Money, AIProvenance, VectorClock)
│ ├── telemetry/ # @ghasi/telemetry — OTel wrapper, log schema v3, redaction
│ ├── event-envelope/ # @ghasi/event-envelope — EventEnvelope<T>, outbox/inbox types
│ ├── api-contracts/ # @ghasi/api-contracts — shared Zod schemas, error codes, request envelope
│ ├── ui/ # @ghasi/ui — design tokens, Radix-wrapped primitives
│ ├── config/ # @ghasi/config — shared eslint, prettier, vitest, tsconfig presets
│ └── sync-protocol/ # @ghasi/sync-protocol — LocalMutation, VectorClock, conflict types
├── event-schemas/ # Git-backed registry: {service}/{aggregate}/{event}/v{N}.json
├── terraform/ # IaC per region / env
├── k8s/ # Helm charts per service
├── (external specs) # https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech — see §0
└── scripts/ # migrations, seeding, backups
Package naming
- Services:
@ghasi/service-<name>published as internal workspace package - Shared:
@ghasi/<domain>(e.g.,@ghasi/domain-primitives,@ghasi/telemetry) - Apps:
@ghasi/app-<surface>(e.g.,@ghasi/app-web-learner)
Turbo pipeline requirements
build,test,lint,typecheck,test:integration,test:contract,test:e2etasks defined on every package- Cache keys include
tsconfig.base.json+turbo.json+ relevant env vars test:integrationdepends on Testcontainers — not cached by default- No task may invoke
pnpm run/ nested package scripts across service boundaries; use workspace package references only
3. Service Architecture (Clean + DDD, ironclad)
Every service under services/<name>/ has four layers. Dependencies flow one direction only: presentation → application → domain ← infrastructure.
Layer rules
src/
├── domain/ # PURE TypeScript. No imports from: nestjs, drizzle, prisma, nats, axios, redis, fs, aws-sdk, openai.
│ # Allowed: crypto stdlib, date-fns, zod (for VO invariants only), @ghasi/domain-primitives.
│ # Contents: aggregates, value objects, domain services, domain events, repository interfaces.
├── application/ # Use cases, command/query handlers, ports (interfaces), DTOs, mappers.
│ # Imports: domain, @ghasi/api-contracts, @ghasi/event-envelope. NEVER imports infrastructure.
│ # Forbidden: process.env, fetch, fs, any concrete adapter.
├── infrastructure/ # Adapters implementing application ports. Postgres repos, NATS pub/sub, HTTP clients, S3, Redis, KMS.
│ # All env parsing + DI wiring lives here. SDK imports ONLY here.
└── presentation/ # NestJS controllers, GraphQL resolvers, WS/SSE handlers. DTO-in, DTO-out — never domain entities leaking.
Enforcement: ESLint import/no-restricted-paths rule blocks violating imports. CI fails on violation.
Required per-service docs (matches https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech)
Location: https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech<name>\ (documentation repository). Do not treat services/<name>/ in this monorepo as the home for those 17 files unless the team explicitly mirrors them (optional); the authoritative set is in the documentation repo.
SERVICE_OVERVIEW.mdDOMAIN_MODEL.mdAPPLICATION_LOGIC.mdAPI_CONTRACTS.mdEVENT_SCHEMAS.mdDATA_MODEL.mdSYNC_CONTRACT.mdAI_INTEGRATION.mdSECURITY_MODEL.mdOBSERVABILITY.mdTESTING_STRATEGY.mdDEPLOYMENT_TOPOLOGY.mdFAILURE_MODES.mdLOCAL_DEV_SETUP.mdSERVICE_READINESS.mdSERVICE_RISK_REGISTER.mdMIGRATION_PLAN.md
Missing any of these in the documentation repo = the service is not ready. Do not scaffold code before all 17 exist there (stubs may be acceptable only where SERVICE_TEMPLATE.md allows; the scaffold command still requires the five critical docs to be materially filled — see commands/scaffold-service.md).
4. Naming Conventions (see https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech)
| Thing | Pattern | Example |
|---|---|---|
| Service dir | {bounded-context}-service | identity-service |
| Workspace pkg | @ghasi/service-{name} | @ghasi/service-identity |
| File: aggregate | kebab-case.ts | course-draft.ts |
| File: value object | kebab-case.ts | tenant-id.ts |
| File: port | {name}.port.ts | ai-client.port.ts |
| File: adapter | {name}.adapter.ts | postgres-course-draft.adapter.ts |
| File: use case | {name}.use-case.ts | publish-course.use-case.ts |
| File: controller | {name}.controller.ts | courses.controller.ts |
| File: spec | *.spec.ts (unit), *.integration.spec.ts, *.contract.spec.ts, *.e2e.spec.ts | |
| Class | PascalCase | CourseDraft, PublishCourseUseCase |
| Interface (port) | PascalCase, no I prefix | AIClient, not IAIClient |
| Branded ID | type {Name}Id = Branded<string, '{Name}Id'> | type CourseDraftId = … |
| Event type | {service}.{aggregate}.{event}.v{N} | authoring.course_draft.published.v1 |
| Event subject (NATS) | same as event type | |
| DB table | snake_case_plural | course_drafts, play_sessions |
| DB column | snake_case | tenant_id, created_at |
| API path | /api/v{N}/{resource-plural} (lowercase, kebab-case) | /api/v1/course-drafts/drf_01H…/blocks |
| Env var | SCREAMING_SNAKE_CASE | DATABASE_URL, NATS_URL |
5. API Contract Rules (see https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech)
Every request MUST carry
Authorization: Bearer <jwt>on tenant-scoped endpointsX-Tenant-Id: <tenantId>— must match JWTtid; mismatch → 403authz.tenant_not_a_memberIdempotency-Key: <ULID>on everyPOST/PUT/PATCHwith body; stored 24htraceparent(W3C) — generated at edge if missingAccept-Language: <BCP-47>If-Match: "<version>"on optimistic-concurrency writes; missing → 428, mismatch → 412
Response envelope (success)
{
"data": { },
"meta": { "requestId": "req_01H…", "apiVersion": "v1.42", "traceId": "…",
"page": { "size": 50, "cursor": "…", "nextCursor": "…" } }
}
Response envelope (error — RFC 9457 problem+json)
{
"error": {
"type": "https://errors.ghasi.io/validation/field_required",
"code": "validation.field_required",
"title": "Missing required field",
"status": 422,
"detail": "Field 'email' is required.",
"instance": "/api/v1/users",
"errors": [{ "field": "email", "code": "required" }],
"traceId": "…", "requestId": "…",
"retriable": false, "retryAfter": null,
"docUrl": "https://docs.ghasi.io/errors/validation/field_required"
}
}
Canonical error codes: https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech.
Pagination
- Cursor-only. Offset pagination is forbidden. Hard cap
page.size = 200. - Cursor is opaque base64url JSON:
{ v: 1, k: {sortFields}, d: "asc|desc", f: "filterFingerprint" }.
Versioning
- Major in path:
/api/v2/…, with ≥1 release of overlap + dual-read. - Minor additive via
X-API-Version: 1.42response header. - Deprecation:
Deprecation: true,Sunset: <RFC 7231 date>,Link: <doc>; rel="deprecation".
6. Event-Driven Rules (see https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech)
Envelope (mandatory on every event)
interface EventEnvelope<T = unknown> {
eventId: ULID;
eventType: string; // '{service}.{aggregate}.{event}' (no version here)
eventVersion: number; // 1, 2, 3…
schemaUri: string; // 'schemas://service/aggregate/event/vN#sha256-…'
source: { service: string; instance: string; commit: string };
occurredAt: ISODate;
ingestedAt: ISODate;
causationId?: ULID;
correlationId: ULID;
tenantId: TenantId;
actor: { type: 'user'|'system'|'api_key'|'service_account'; id: string };
payload: T;
partitionKey: string; // usually aggregateId
outbox?: { dbWriteTs: ISODate; outboxId: ULID };
retentionClass: 'operational'|'regulated'|'audit';
dataResidency: 'us'|'eu'|'me'|'ap';
}
Implement once in @ghasi/event-envelope. Reuse everywhere.
Outbox pattern (producers — mandatory)
- Every domain state change that emits events writes to the
outboxtable in the SAME transaction as the aggregate mutation. OutboxRelayworker polls unpublished rows, publishes to NATS, markspublished_at, retries exponential with jitter.- No direct NATS publish from use cases. Go through the outbox.
Inbox pattern (consumers — mandatory)
inboxtable:event_idPK +consumer+processed_at+result.- Check inbox before applying; if present → no-op (cached result).
- Apply mutation + insert inbox in same transaction.
Schema registry
event-schemas/{service}/{aggregate}/{event}/v{N}.json— JSON Schema.- CI validates publish + consume against registry.
- Breaking change → new
vN, dual-publish window ≥1 release.
7. Data + Tenancy Rules (ironclad, see https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech, https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech)
Every table
id(PK, ULID, TEXT or UUIDv7)tenant_id UUID NOT NULL— alwayscreated_at timestamptz NOT NULLupdated_at timestamptz NOT NULLdeleted_at timestamptz NULL— soft delete (never hard-delete)version bigint NOT NULL DEFAULT 1— optimistic locking
RLS on every table
ALTER TABLE <t> ENABLE ROW LEVEL SECURITY;
CREATE POLICY <t>_tenant_isolation ON <t>
USING (tenant_id = current_setting('app.tenant_id')::uuid);
- Gateway sets
SET LOCAL app.tenant_id = '<uuid>'per request (PgBouncer init or connection wrapper). - Repository methods must require
tenantIdparameter; never implicit. - Domain layer: every aggregate root holds a
TenantIdVO. Constructors reject cross-tenant entity refs.
IDs
- ULID or UUIDv7, always. Wrap in branded types (
type XxxId = Branded<string, 'XxxId'>). - URL prefix convention:
crs_(course),drf_(draft),lsn_(lesson),usr_(user),ten_(tenant),org_,evt_,req_,dev_, etc. — choose the prefix in theDATA_MODEL.mdof the owning service.
Timestamps
- Wire format: RFC 3339 UTC (
2026-04-16T10:00:00.000Z). - Never epoch millis as primary representation.
- Client converts to local for display.
Soft delete
- Set
deleted_at = now(). NeverDELETErows (except via GDPR erasure saga). - Read paths filter
deleted_at IS NULLby default.
8. Security + AuthN/Z (see https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech)
- Passwords: argon2id (
m=64 MiB, t=3, p=1). No other hash algo for passwords. - JWT: EdDSA Ed25519, KMS-backed,
kidrotation, JWKS at/.well-known/jwks.json. - Access token TTL: 15 min max. Refresh: 30d sliding, single-use rotation, family-revoke on reuse.
- Required JWT claims:
iss, aud, sub, tid, tids, roles, scope, did, amr, iat, exp, jti, v. - MFA: TOTP / WebAuthn / recovery codes. SMS deprecated.
- Secrets: KMS + Vault. Never in source, env files committed to Git, or logs.
- Encryption at rest: AES-256 with per-tenant KMS data keys (envelope) for Confidential+.
- Offline bundles: AES-256-GCM, device-derived key via
HKDF(tenantKey, devicePubKey, bundleId). - PII deny-list in logs:
password, otp, access_token, payment_pan, national_id, email, learner_free_text_answer, health_note. Enforced by@ghasi/telemetryredactor.
9. Observability (see https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech)
- Instrument via
@ghasi/telemetryonly. No direct vendor SDKs. - Logs: JSON, schema v3; required keys
ts, level, msg, event, service, trace_id, span_id, request_id, tenant_id, actor_id_hash, app, env, region, attrs, log_schema_version. msgstatic; variables go inattrs(snake_case, namespaced).- Trace sampling: health 0%, cached reads 1%, writes 10%, 100% for payments, scoring, DSAR, AI inference, offline sync replay; errors & slow tail-override to 100%.
- Every span on DB / cache / HTTP / NATS / S3 / AI outbound carries:
otel.status_code,error.type,ghasi.tenant_id, plus domain attributes (ghasi.course_id,ghasi.ai.model,ghasi.ai.cost_usd, …). - SLIs per service: Availability 99.9%, P95 ≤ 300 ms API, P99 ≤ 800 ms, saturation < 0.8, queue lag < 5000/partition.
10. Testing (see https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech)
Pyramid
| Layer | Target coverage | Gate |
|---|---|---|
| Unit (domain-pure, no I/O) | ≥90% line/branch on aggregates, 100% on VOs | Merge blocker |
| Mutation (Stryker, changed files) | ≥75% aggregates, ≥85% VOs | Merge blocker |
| Integration (Testcontainers: Postgres/Redis/NATS/OpenSearch/MinIO/Mailpit) | ≥80% | Merge blocker |
| Contract (Pact for HTTP, Avro / JSON Schema for events) | All public endpoints + events | Merge blocker |
| E2E (Playwright, 10 critical journeys) | 100% journeys green on nightly | Merge blocker |
| A11y (axe-playwright) | Zero new serious/critical | Merge blocker |
| Lighthouse budgets | Within per-page budgets | Hard block if >10% over |
| Load (k6 / Locust) | Nightly staging | Observation |
| AI eval (golden set + safety adversarial) | ≥parity, zero safety violations | Merge blocker on AI changes |
Mandatory per service
test/integration/tenant-isolation.spec.ts— two tenants, identical IDs, prove RLS blocks cross-tenant reads.test/contract/— Pact + event schema conformance tests.- One E2E journey minimum.
Test naming
given_<setup>_when_<action>_then_<outcome>.- Builders live in
__builders__/colocated with aggregate.
Non-negotiables
- Every bug fix ships with a regression test. PR template enforces this.
- Flaky tests quarantined within 24h; fix-or-delete within 5 business days (max 10 with QA-lead exception).
- No PR merges without green CI (stages 1–10).
- No production deploy without canary (5% / 30 min) + verified rollback plan.
11. Frontend Rules (see https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech, https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech)
11.0 Multi-platform frontend specs (docs/frontend/**) — mandatory
Before implementing or refactoring web, mobile shell, or desktop shell code (apps/web-application, apps/mobile-shell, apps/desktop-shell, or shared UI that implements those contracts):
- Read
{docs-root}/docs/frontend/README.mdfor the index and hierarchy (how these specs relate to08–11). - Read the platform specification for the surface you change:
- Web:
docs/frontend/web/04-web-app-specification.md - Mobile (Capacitor):
docs/frontend/mobile/05-mobile-app-specification.md - Desktop (Electron):
docs/frontend/desktop/06-desktop-app-specification.md
- Web:
- When changing cross-cutting client architecture (offline, sync, routing, state patterns), read
docs/frontend/common/02-architecture-overview.mdand any othercommon/files the README lists as prerequisites.
If the implementation intentionally differs from the reference repo layout in those specs (for example a consolidated web-application package instead of five separate Next apps), document the mapping in the story output / PR / workflow-state.md — do not treat “not in spec” as permission to skip reading the spec.
- Next.js 14+ App Router, React Server Components where appropriate.
- Design tokens as CSS custom properties at
:root. Use logical CSS (padding-inline,margin-inline-start) — LTR + RTL both must work. - Every component has RTL Storybook story + both-direction visual regression.
- Accessibility: WCAG 2.2 AA. Contrast ≥ 4.5:1 body, ≥ 3:1 large/UI. Focus visible. Semantic HTML first, ARIA minimal.
- All animations gated on
prefers-reduced-motion. No exceptions. - State: TanStack Query (server) + Zustand (client UI). Redux forbidden.
- Forms: React Hook Form + Zod schemas shared with backend via
@ghasi/api-contracts. - i18n: ICU MessageFormat. Every user-facing string goes through translation pipeline. No hardcoded strings.
- Performance budgets enforced in Lighthouse CI: see
https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech. - Offline UX patterns mandatory where applicable: Sync Status Pill, Sync Center, Bundle Manager, optimistic mutations, conflict UI.
12. AI Rules (ironclad)
ai-gateway-serviceis the only egress point for LLM / embedding / TTS / moderation / image calls.- No direct
openai,anthropic,cohere, etc. imports outside that service. ESLintno-restricted-importsblocks this. - Every AI-generated artifact persisted with
AIProvenanceVO:interface AIProvenance {model: string; version?: string;promptId?: string; promptVersion?: SemVer;traceId: string; decisionId?: string;local: boolean; generatedAt: ISODate;reviewedBy?: UserId; reviewedAt?: ISODate;cost?: { microUSD: number; tokens: { in: number; out: number } };safety: { input: SafetyVerdict; output: SafetyVerdict };cacheHit: boolean;} - AI-marked blocks persist as
status: draft_ai. Promotion toreviewedrequires a humandecisionId. Domain invariant — cannot be bypassed. - Moderation: pre-call (input policy + PII redaction + prompt-injection shield) and post-call (output policy + structured-output validation).
- Feature flags: default-off for AI features, per-tenant opt-in.
- Provider training on tenant data: explicitly disabled; verified at integration layer.
- Tenant-scoped embeddings only. Never cross-tenant k-NN.
13. Working Rules for AI Assistants
Before writing any code
- Confirm bounded context. Read
SERVICE_OVERVIEW.md+DOMAIN_MODEL.mdunderhttps://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech<name>\. - Check the relevant spec. Table at top of this file.
- Check existing patterns. Grep the service for similar aggregates/use-cases/adapters. Mirror them.
- If the service doesn't exist yet, create the 17 docs in the documentation repo (
docs/standards/SERVICE_TEMPLATE.md) — all 17 docs first there, then run/scaffold-servicein this monorepo for code only.
When touching existing code
- Never widen layer-crossing imports. If you need infrastructure in a use case, add a port in
application/ports/and an adapter ininfrastructure/adapters/. - Never add columns without RLS + tenant_id.
- Never publish events without outbox.
- Never add endpoints without updating
API_CONTRACTS.md, OpenAPI, and Pact consumer tests. - Never add AI calls outside
ai-gateway-service. - Bug fixes ship with regression tests — same commit, not a follow-up.
Definition of Done
See https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech. Every PR must pass it.
Communication
- When the user asks for a new service: respond with the SERVICE_TEMPLATE checklist, confirm the bounded context, and create the docs in
https://github.com/ghasi-EdTech/ghasi-e-documentation/blob/master/D/Ghasi-edTech<name>\first — then scaffold code in this repo, not specs. - When spec and code disagree: the spec wins. Flag the discrepancy and either update code or, with user's explicit approval, update the spec.
- When a new rule emerges from discussion: offer to add it to this file or the relevant standards doc — don't keep it in chat-only memory.
14. Ironclad MUST / NEVER
MUST
- Domain layer stays framework-free and I/O-free.
- Every service owns its data; cross-service reads go through APIs or events + projections.
- All APIs and events are versioned.
- Tenant isolation enforced at every layer (edge, gateway, app, domain, DB RLS, storage, cache, search, vectors, logs, backups).
- Outbox for every event; inbox for every consumer.
- Idempotency-Key required on all writes.
- Branded IDs (ULID or UUIDv7). Never raw UUID, never autoincrement.
- Migrations backward-compatible within a major version; dual-read windows for breaking changes.
- Integration tests use Testcontainers, not mocks of infrastructure.
- OpenTelemetry-only instrumentation via
@ghasi/telemetry. - AIProvenance VO on every persisted AI-generated artifact.
- Every bug fix has a regression test.
NEVER
- Never import framework/ORM/IO into
domain/. - Never hardcode secrets; never commit env files with secrets.
- Never share DB schemas across services.
- Never use offset pagination.
- Never emit PII or sensitive data to logs (see deny-list in §8).
- Never bypass RLS; never skip setting
app.tenant_id. - Never call LLM providers directly outside
ai-gateway-service. - Never persist AI-generated content without
AIProvenance. - Never promote
draft_aitoreviewedwithout humandecisionId. - Never ship a new event without registry entry + consumer contract test.
- Never ship an endpoint without OpenAPI + Pact + updated
API_CONTRACTS.md. - Never merge with flaky tests, serious/critical a11y violations, or failing CI.
- Never exceed 15 min access-token TTL or 30d refresh-token TTL.
- Never store plaintext passwords (argon2id only).
- Never use
eval,dangerouslySetInnerHTML, orexecon user input.
When in doubt: read the spec. When the spec is silent: match existing patterns. When both are silent: ask the user before inventing.