Naming
One page. Every AI edit must respect these. When ambiguous, match the closest existing pattern in the same service.
Services + packages
| Thing | Pattern | Example |
|---|---|---|
| Service dir | <noun>-service | reservation-service, lock-integration-service, ai-orchestrator-service |
| Workspace package (service) | @ghasi/service-<name> | @ghasi/service-reservation, @ghasi/service-lock-integration |
| Workspace package (shared lib) | @ghasi/<domain> | @ghasi/domain-primitives, @ghasi/telemetry, @ghasi/event-envelope, @ghasi/api-contracts, @ghasi/sync-protocol, @ghasi/ui-melmastoon, @ghasi/config |
| Workspace package (app) | @ghasi/app-<surface> | @ghasi/app-web-meta, @ghasi/app-web-tenant-booking, @ghasi/app-mobile-consumer, @ghasi/app-desktop-backoffice |
The 22 canonical service names are listed in docs/03-microservices/README.md. Never invent a new service name without adding it to that catalog in the same PR.
Files
| Thing | Pattern | Example |
|---|---|---|
| Aggregate | kebab-case.ts | reservation.ts, key-credential.ts, rate-plan.ts |
| Value object | kebab-case.ts | tenant-id.ts, money.ts, date-range.ts |
| Domain event | kebab-case.event.ts | reservation-confirmed.event.ts, key-credential-revoked.event.ts |
| Domain error | kebab-case.error.ts | cross-tenant-reference.error.ts, overbooking-blocked.error.ts |
| Port (interface) | <name>.port.ts | lock.port.ts, payment.port.ts, reservation.repository.port.ts |
| Adapter (impl) | <vendor-or-tech>-<name>.adapter.ts | postgres-reservation.adapter.ts, pubsub-event-publisher.adapter.ts, ttlock-lock.adapter.ts, salto-lock.adapter.ts |
| Use case | <verb-noun>.use-case.ts | confirm-reservation.use-case.ts, issue-key-credential.use-case.ts |
| DTO (application) | <name>.dto.ts | confirm-reservation-command.dto.ts |
| Mapper | <name>.mapper.ts | reservation.mapper.ts |
| Controller | <resource>.controller.ts | reservations.controller.ts, key-credentials.controller.ts |
| Guard | <name>.guard.ts | jwt-auth.guard.ts, tenant-context.guard.ts |
| Unit spec | *.spec.ts | reservation.spec.ts |
| Integration spec | *.integration.spec.ts | confirm-reservation.integration.spec.ts |
| Contract spec | *.pact.spec.ts or *.schema.spec.ts | reservations.pact.spec.ts |
| E2E spec | *.e2e.spec.ts | walk-in-checkin-offline.e2e.spec.ts |
| React component | PascalCase.tsx | RoomCard.tsx, BookingFlow.tsx |
| React hook | use-kebab-case.ts | use-sync-status.ts, use-tenant-theme.ts |
| CSS / styles | kebab-case.module.css | room-card.module.css |
| SQL migration | NNNN_<description>.sql (zero-padded ordinal) | 0007_add_key_credentials_table.sql |
Symbols
| Thing | Pattern | Example |
|---|---|---|
| Class | PascalCase | Reservation, ConfirmReservationUseCase, TtLockAdapter |
| Interface (port) | PascalCase, no I prefix | LockPort, PaymentPort, ReservationRepository |
| Type alias | PascalCase | ConfirmReservationCommand, BookingFlowStep |
| Branded ID | type <Entity>Id = Branded<string, '<Entity>Id'> | type TenantId = Branded<string, 'TenantId'>, type ReservationId = Branded<string, 'ReservationId'>, type KeyCredentialId = Branded<string, 'KeyCredentialId'>, type PropertyId = Branded<string, 'PropertyId'>, type RoomId = Branded<string, 'RoomId'>, type FolioId = Branded<string, 'FolioId'> |
| Constant | SCREAMING_SNAKE_CASE | MAX_PAGE_SIZE, DEFAULT_HOLD_TTL_SECONDS |
| Enum | PascalCase type + PascalCase members | enum ReservationStatus { Held, Confirmed, CheckedIn, CheckedOut, Cancelled, NoShow } |
| Variable / function | camelCase | confirmReservation, currentTenantId |
| React component | PascalCase | <SyncStatusPill />, <KeyCredentialCard /> |
| React hook | use prefix | useSyncStatus(), useTenantTheme() |
Events
Event subject format: melmastoon.<service>.<aggregate>.<verb-past-tense>.v<n>
service: the service short name (matches service dir minus-servicesuffix). Examples:reservation,inventory,lock-integration,ai-orchestrator.aggregate: snake_case aggregate name.verb-past-tense: snake_case past-tense verb (one event = one fact that already happened).v<n>: integer, starting atv1.
Examples:
melmastoon.reservation.booking.confirmed.v1melmastoon.reservation.booking.cancelled.v1melmastoon.inventory.allocation.committed.v1melmastoon.pricing.rate_plan.published.v1melmastoon.billing.folio.posted.v1melmastoon.payment.intent.captured.v1melmastoon.lock_integration.key_credential.issued.v1melmastoon.lock_integration.key_credential.revoked.v1melmastoon.housekeeping.task.completed.v1melmastoon.ai_orchestrator.completion.refused.v1melmastoon.search_aggregation.listing.indexed.v1
GCP Pub/Sub topic name = event subject (identical, dot-separated). Subscribers are named <consumer-service>.<topic> (e.g. inventory.melmastoon.reservation.booking.confirmed.v1).
Database
| Thing | Pattern | Example |
|---|---|---|
| Table | snake_case_plural | reservations, key_credentials, rate_plans, outbox, inbox |
| Column | snake_case | tenant_id, created_at, is_confirmed, check_in_at |
| Index | ix_<table>_<columns> | ix_reservations_tenant_id_status, ix_key_credentials_reservation_id |
| Foreign key | fk_<table>_<referenced_table> | fk_key_credentials_reservations |
| RLS policy | <table>_tenant_isolation | reservations_tenant_isolation, key_credentials_tenant_isolation |
| Migration | NNNN_<description>.sql (per-service, zero-padded ordinal) | 0006_add_reservations_table.sql |
Every multi-tenant table has tenant_id uuid NOT NULL plus an RLS policy. Money is stored as bigint micro-units (no floats); the column suffix is _micro (e.g., total_amount_micro).
API paths
/api/v<N>/<resource-plural-kebab>with optional nested sub-resources.- Lowercase, kebab-case, no trailing slash.
- Resource IDs are ULIDs with the service-chosen prefix.
Examples:
POST /api/v1/reservationsGET /api/v1/reservations/rsv_01H.../folioPATCH /api/v1/reservations/rsv_01H...POST /api/v1/reservations/rsv_01H.../check-inPOST /api/v1/key-credentialsDELETE /api/v1/key-credentials/key_01H...GET /api/v1/properties/ppt_01H.../roomsPOST /api/v1/sync/v1/pullPOST /api/v1/sync/v1/push
BFF routes are namespaced by surface: /bff/consumer/v1/..., /bff/tenant-booking/v1/..., /bff/backoffice/v1/....
Error codes
Format: MELMASTOON.<DOMAIN>.<CODE> — three uppercase, dot-separated segments, snake_case-uppercase code suffix.
Examples:
MELMASTOON.RESERVATION.OVERBOOKING_BLOCKEDMELMASTOON.INVENTORY.ALLOCATION_NOT_FOUNDMELMASTOON.PAYMENT.GATEWAY_TIMEOUTMELMASTOON.LOCK.VENDOR_UNREACHABLEMELMASTOON.SYNC.CURSOR_OUT_OF_RANGEMELMASTOON.AI.REFUSED_SAFETY
The full registry lives in ERROR_CODES.md. Never invent a new code in a PR without adding it there.
ID prefixes (declared in each service's DATA_MODEL.md)
Reserved prefixes (the canonical registry):
| Prefix | Entity | Owning service |
|---|---|---|
tnt_ | Tenant | tenant-service |
usr_ | User (auth subject) | iam-service |
mbr_ | Membership (user-in-tenant role) | tenant-service |
ppt_ | Property | property-service |
rmt_ | RoomType | property-service |
rmu_ | Room (sellable unit) | property-service |
rsv_ | Reservation | reservation-service |
bkg_ | Booking (in-progress) | reservation-service |
gst_ | Guest | reservation-service |
rate_ | RatePlan | pricing-service |
inv_ | InventoryAllocation | inventory-service |
hkt_ | HousekeepingTask | housekeeping-service |
mnt_ | MaintenanceTicket | maintenance-service |
fol_ | Folio | billing-service |
chg_ | Charge | billing-service |
inv_doc_ | Invoice (document) | billing-service |
pay_ | Payment | payment-gateway-service |
pyi_ | PaymentIntent | payment-gateway-service |
key_ | KeyCredential | lock-integration-service |
lck_ | LockDevice | lock-integration-service |
stf_ | StaffMember | staff-service |
shf_ | Shift | staff-service |
ntf_ | Notification | notification-service |
tpl_ | Template (notification, theme) | notification-service / theme-config-service |
thm_ | Theme | theme-config-service |
med_ | MediaAsset | file-storage-service |
srh_ | SearchableDocument | search-aggregation-service |
dec_ | Decision (AI HITL) | ai-orchestrator-service |
dev_ | Device (desktop binding) | iam-service |
evt_ | Event (envelope) | platform-wide |
req_ | Request (correlation) | platform-wide |
When adding a new entity, pick a 3–4-character prefix, document it in the owning service's DATA_MODEL.md, and add a row to this table in the same commit.
Environment variables
SCREAMING_SNAKE_CASE.- Namespaced by concern:
DATABASE_URL,REDIS_URL,PUBSUB_PROJECT_ID,KMS_KEY_RESOURCE,AI_ORCHESTRATOR_BASE_URL,OTEL_EXPORTER_OTLP_ENDPOINT. - Service-local vars namespaced by service short name:
IAM_JWT_ISSUER,RESERVATION_HOLD_TTL_SECONDS,LOCK_TTLOCK_API_KEY. - Validated at startup via Zod schema in
src/infrastructure/config/env.ts. Fail fast if missing.