CrowdVision · source-available, not open source · © 2026 Nicolò Ghignatti
Consistent naming lets a reader infer an artefact’s role from its name alone. CrowdVision spans several languages and layers, so the conventions are defined per layer below. They are enforced partly by ESLint and partly by review; treat them as mandatory.
A service carries one short name across every surface it appears in — no -service suffix anywhere. The suffix is redundant inside backend/, and having it on some surfaces but not others is how a service_key ends up disagreeing with a directory.
| Surface | Example |
|---|---|
| Directory | backend/telemetry, backend/digital-twin |
| Cargo crate and compiled binary | telemetry, digital-twin |
| Docker image | crowdvision-telemetry |
| Container name / hostname other services resolve | telemetry:3000 |
| Environment variable | TELEMETRY_URL, DIGITAL_TWIN_URL |
.github/services.json key, moon project, CI leg | telemetry |
| Kubernetes manifest, documentation page | k8s/deployments/telemetry.yml, services/telemetry.qd |
| Diagram asset | architecture/images/cc-telemetry.svg, architecture/images/cc-digital-twin.svg |
claims-gateway and provisioner are named for what they are — a gateway, a reconciler — rather than <noun>-service. That is deliberate, not an oversight.
Top-level placement follows the same principle: the directory says what kind of thing it holds.
| Path | Holds |
|---|---|
backend/<name>/ | A deployable service. |
backend/libs/ | Shared Go modules embedded into services, never deployed. |
backend/acceptance/ | The cross-service acceptance suite. Each service keeps its own tests/ for its own suite. |
schemas/ | Every shape that crosses a service boundary: json/ specs, fixtures/, and the Rust binding crates. |
Every Node service follows the same internal layout, so the file you want is always in the same place.
| Path | Holds |
|---|---|
src/router.ts | Route declarations. |
src/controller/ | HTTP adapters (one file per concern). |
src/services/ | Business logic. |
src/models/ | Schemas and shared types. |
src/middlewares/ | Cross-cutting middleware. |
src/config/ | Environment accessors and client setup. |
__tests__/ | The service’s test suite. |
| Artefact | Convention | Recommended | Avoid |
|---|---|---|---|
| Controller / service files | camelCase, layer as suffix where it adds clarity | buildings.ts, notificationService.ts | twin_ctrl.ts, Notification.ts |
| Model files | camelCase file name | account.ts, buildingThreshold.ts | Account.ts, building_threshold.ts |
| Exported model and interface | PascalCase; interfaces prefixed I | export interface IAccount, export const Account = model(...) | export const account, export interface Account |
| Functions and variables | camelCase | generateStandardToken, allowedColumns | GenerateToken, allowed_columns |
| Constants | UPPER_SNAKE_CASE | ROLE_WEIGHTS, COOKIE_NAME | roleWeights |
| Database collections | plural, lowercase | accounts, buildings | Account, building |
| Artefact | Convention | Recommended | Avoid |
|---|---|---|---|
| Components | PascalCase, multi-word (to avoid clashing with HTML elements), file and tag | BuildingCard.vue, NotificationDropdown.vue, LeftMenu.vue | card.vue, header.vue |
| Views / pages (top-level routes) | PascalCase with a View suffix | DashboardView.vue, AdministrationView.vue | Dashboard.vue |
| Composables | camelCase, prefixed use | useBuildingModel.ts, useAuth.ts, useSceneControls.ts | buildingModel.ts, getAuth.ts |
| Utilities / helpers | camelCase | metrics.ts, formatTimestamp.ts | Metrics.ts |
Both Rust services follow idiomatic Rust style.
| Artefact | Convention | Example |
|---|---|---|
| Modules and files | snake_case | tunnel.rs, discovery.rs |
| Functions | snake_case | start_telemetry_tunnel, metric_is_allowed |
| Types (structs, enums) | PascalCase | AppState, PreferenceDocument |
| Constants | SCREAMING_SNAKE_CASE | RAW_CHANNEL, DEFAULT_COLUMNS |
claims-gateway, tenancy, registry, and provisioner follow idiomatic Go style (gofmt, checked by go vet in just lint).
| Artefact | Convention | Example |
|---|---|---|
| Packages and files | snake_case or single lowercase word, matching the package name | internal/api/handler.go, internal/store/ |
| Exported functions and types | PascalCase | func Mount(...), type Config struct |
| Unexported functions, types, and variables | camelCase (Go’s own export rule: capitalisation is visibility, not a separate style choice) | func writeDomain(...), type handler struct, func isValidAccountID(...) |
| Constants | PascalCase or camelCase per Go convention (no SCREAMING_SNAKE_CASE — that’s not idiomatic Go) | as appropriate to exported/unexported |
Package layout follows the standard Go project layout: internal/api (HTTP handlers), internal/service (business logic), internal/store (persistence, often with a storefake sibling for tests), internal/events (Redis Stream consumers/producers), internal/config.
The agent follows PEP 8: snake_case for modules, functions, and variables; PascalCase for classes; SCREAMING_SNAKE_CASE for constants. (The agent is maintained separately; these conventions are listed for completeness.)
Branch names mirror the commit types, so the branch announces its intent.
<type>/<short-kebab-description>| Type | Example |
|---|---|
feat | feat/private-domains |
fix | fix/jwt-expiry-off-by-one |
chore | chore/bump-dependencies |
docs | docs/telemetry-api-reference |
refactor | refactor/extract-token-service |
Conventional-commit scopes use the service or area name, so history can be filtered by component.
feat(twin): add per-room colour overrides
fix(gateway): reject expired refresh tokens
docs(telemetry): document the ingestion fast path
chore(ci): pin the kubeconform versionCommon scopes: gateway (claims-gateway), tenancy, registry, provisioner, twin, telemetry, notification, socket, contracts, client, agent, ci, docs, k8s. The full commit format is covered in Commits & Release Please.
| Artefact | Convention | Example |
|---|---|---|
| Environment variables | UPPER_SNAKE_CASE | GATEWAY_ISSUER, TELEMETRY_URL |
| Docker images | crowdvision-<service> under the org namespace | ghcr.io/nickghignatti/crowdvision-twin |
| Workflow files | <category>-<name> with a yml extension (ci-, cd-, maint-) | ci-twin.yml, cd-release.yml, maint-cleanup.yml |
| Documentation pages | kebab-case with a qd extension | building-data.qd, naming-conventions.qd |
A rename is never only a rename: several identifiers derived from a service’s name own state, and changing one silently starts from empty rather than failing.
| Identifier | What a rename actually does |
|---|---|
| Compose volume | Compose derives the real volume name from the project plus the declared name, so renaming contracts-data to dashboard-data mounts a new, empty volume and leaves the old one orphaned with all the data still in it. Migrate the contents in the same change, or the service comes up looking healthy and blank. This is what happened to the dashboard’s saved column preferences when contracts became dashboard. |
| Kafka consumer group | A group with no committed offsets plus auto.offset.reset=earliest re-reads the whole topic — a replay, not a reset. Idempotent for the registration groups; for alerts it re-delivers historical breaches, collapsed only by the Redis cooldown. |
| Database name | Renaming a database is a migration. chatdb, twindb and contractsdb keep their names for that reason, and none carried a -service suffix to begin with. |
| System identity at a trust boundary | system:notification-service keeps the old name on purpose: it is pinned byte-for-byte by a test, and it reads as a stable source in a central log regardless of the directory name. |
docker volume ls lists every volume for the project. Anything whose name no longer appears in a docker-compose YAML file is orphaned — either migrate it or delete it deliberately, but do not leave it to be rediscovered as “the data disappeared”.