Naming
One page. Every AI edit must respect these. When ambiguous, match the closest existing pattern in the service.
Services + packages
| Thing | Pattern | Example |
|---|---|---|
| Service dir | {bounded-context}-service | identity-service, ai-gateway-service |
| Workspace package (service) | @ghasi/service-{name} | @ghasi/service-identity |
| Workspace package (shared lib) | @ghasi/{domain} | @ghasi/domain-primitives, @ghasi/telemetry, @ghasi/event-envelope, @ghasi/api-contracts, @ghasi/sync-protocol, @ghasi/ui, @ghasi/config |
| Workspace package (app) | @ghasi/app-{surface} | @ghasi/app-web-learner, @ghasi/app-web-authoring |
Files
| Thing | Pattern | Example |
|---|---|---|
| Aggregate | kebab-case.ts | course-draft.ts |
| Value object | kebab-case.ts | tenant-id.ts, email-address.ts |
| Domain event | kebab-case.event.ts | course-draft-published.event.ts |
| Domain error | kebab-case.error.ts | cross-tenant-reference.error.ts |
| Port (interface) | {name}.port.ts | ai-client.port.ts, course-draft.repository.port.ts |
| Adapter (impl) | {name}.adapter.ts | postgres-course-draft.adapter.ts, nats-event-publisher.adapter.ts |
| Use case | {verb-noun}.use-case.ts | publish-course.use-case.ts |
| DTO (application) | {name}.dto.ts | publish-course-command.dto.ts |
| Mapper | {name}.mapper.ts | course-draft.mapper.ts |
| Controller | {resource}.controller.ts | courses.controller.ts |
| Guard | {name}.guard.ts | jwt-auth.guard.ts |
| Unit spec | *.spec.ts | course-draft.spec.ts |
| Integration spec | *.integration.spec.ts | publish-course.integration.spec.ts |
| Contract spec | *.pact.spec.ts or *.schema.spec.ts | courses.pact.spec.ts |
| E2E spec | *.e2e.spec.ts | learner-first-lesson.e2e.spec.ts |
| React component | PascalCase.tsx | CourseCard.tsx |
| React hook | use-kebab-case.ts | use-sync-status.ts |
| CSS / styles | kebab-case.module.css | course-card.module.css |
Symbols
| Thing | Pattern | Example |
|---|---|---|
| Class | PascalCase | CourseDraft, PublishCourseUseCase |
| Interface (port) | PascalCase, no I prefix | AIClient, CourseDraftRepository |
| Type alias | PascalCase | PublishCourseCommand |
| Branded ID | type {Entity}Id = Branded<string, '{Entity}Id'> | type UserId = Branded<string, 'UserId'> |
| Constant | SCREAMING_SNAKE_CASE | MAX_PAGE_SIZE |
| Enum | PascalCase type + PascalCase members | enum CourseDraftStatus { Draft, DraftAi, Reviewed, Published } |
| Variable / function | camelCase | publishCourse, currentTenantId |
| React component | PascalCase | <SyncStatusPill /> |
| React hook | use prefix | useSyncStatus() |
Events
Event type format: {service}.{aggregate}.{event}.v{N}
service: one of the 19 canonical names (lowercase, matches service dir minus-servicesuffix).aggregate: snake_case.event: snake_case, past tense verb or state.v{N}: integer, starting at 1.
Examples:
authoring.course_draft.published.v1progress.statement.recorded.v2marketplace.order.paid.v1identity.user.registered.v1ai.completion.refused.v1
Subject (NATS) = event type (identical).
Database
| Thing | Pattern | Example |
|---|---|---|
| Table | snake_case_plural | course_drafts, play_sessions, outbox, inbox |
| Column | snake_case | tenant_id, created_at, is_published |
| Index | ix_{table}_{columns} | ix_course_drafts_tenant_id_status |
| Foreign key | fk_{table}_{referenced_table} | fk_lessons_course_drafts |
| RLS policy | {table}_tenant_isolation | course_drafts_tenant_isolation |
| Migration | YYYYMMDDHHMMSS_{description}.sql | 20260416093000_add_course_drafts_table.sql |
API paths
/api/v{N}/{resource-plural-kebab}with optional nested sub-resources.- Lowercase, kebab-case, no trailing slash.
- Resource IDs are ULIDs with service-chosen prefix.
Examples:
POST /api/v1/course-draftsGET /api/v1/course-drafts/drf_01H.../blocksPATCH /api/v1/course-drafts/drf_01H.../blocks/blk_01H...DELETE /api/v1/course-drafts/drf_01H...
ID prefixes (declared in each service's DATA_MODEL.md)
Common conventions (reserve these in the canonical registry):
| Prefix | Entity |
|---|---|
ten_ | Tenant |
usr_ | User |
org_ | OrgUnit |
dev_ | Device |
req_ | Request |
evt_ | Event |
crs_ | Course |
drf_ | CourseDraft |
lsn_ | Lesson |
blk_ | Block |
asn_ | Assignment |
atp_ | AssessmentAttempt |
enr_ | Enrollment |
ord_ | Order |
lic_ | License |
cer_ | Certificate |
bun_ | PlayPackage Bundle |
med_ | MediaAsset |
ntf_ | Notification |
smt_ | Statement (xAPI) |
ses_ | PlaySession |
dec_ | Decision (AI HITL) |
When adding a new entity, pick a three-letter prefix, document in the owning service's DATA_MODEL.md, and add to this table in the same commit.
Environment variables
SCREAMING_SNAKE_CASE.- Namespaced by concern:
DATABASE_URL,REDIS_URL,NATS_URL,KMS_KEY_ARN,AI_GATEWAY_BASE_URL,OTEL_EXPORTER_OTLP_ENDPOINT. - Service-local vars namespaced:
IDENTITY_JWT_ISSUER,AUTHORING_STORAGE_BUCKET. - Validated at startup via Zod schema in
src/infrastructure/config/env.ts. Fail fast if missing.