Companion documents:
README.md(overview & setup),ARCHITECTURE.md(how the system is built). This document is the exhaustive reference: every backend module, model field, enum, derived formula, endpoint, frontend page, and data-entry path. Keep it in sync with the code — see Maintenance & changelog.
- Global conventions
- Roles & RBAC
- Backend modules
- auth · users · teams · projects
- incidents · techDebt · architecture
- oneOnOnes · metrics · dashboard · ai
- okrs · org · investment
- brief · scorecard · engagement
- finance · integrations
- Finance ledgers & formulas
- Frontend reference
- Data provenance matrix
- Internationalization
- Seed data
- Testing
- Maintenance & changelog
Base URL: http://localhost:4000/api/v1 (configurable via API_PREFIX).
Auth: send Authorization: Bearer <accessToken> on protected routes.
Response envelope
- Success:
{ "success": true, "data": ... }. Lists add"meta"pagination. - Error:
{ "success": false, "error": { "code", "message", "details?" } }.
List query params (parsed by shared/utils/query.ts, honored by every
BaseRepository.paginate):
page(default 1),limit(default 20, max 100)sort— comma list,-prefix = descending (e.g.sort=-createdAt,name)search— case-insensitive regex across the resource's searchable fields- resource-specific
allowedFilters(e.g.status,severity,provider,year,month,team)
Pagination meta: { total, page, limit, totalPages }.
Validation: zod schemas in each module's *.dto.ts, applied as
validate({ body, params, query }) middleware. Invalid input → 400 with
details.
| Role | Rank |
|---|---|
admin |
100 |
cto |
90 |
head_of_engineering |
80 |
engineering_manager |
60 |
engineer |
30 |
viewer |
10 |
Defined in server/src/shared/types/index.ts (ROLES, ROLE_RANK).
authorizeAtLeast(role) permits any caller whose rank ≥ the named role.
Each module lives under server/src/modules/<name> (except integrations, which
is server/src/integrations). Fields below are the schema's own fields; all
schemas also carry Mongoose timestamps (createdAt/updatedAt) and an _id.
Mounted at /auth. Register/login, JWT issuance, refresh rotation.
| Method | Path | Auth | Body |
|---|---|---|---|
| POST | /auth/register |
public | { name, email, password, role?, title? } |
| POST | /auth/login |
public | { email, password } |
| POST | /auth/refresh |
public | { refreshToken } |
| POST | /auth/logout |
any | — (revokes refresh tokens) |
| GET | /auth/me |
any | — |
| POST | /auth/change-password |
any | { currentPassword, newPassword } |
login/register return { user, accessToken, refreshToken }. Access TTL
15m, refresh TTL 7d; refresh tokens carry a tokenId stored on the user,
rotated on use, capped at 5 sessions. A stricter rate limiter guards /auth.
Mounted at /users. Model User.
Fields: name, email (unique), password (select:false, bcrypt),
role (enum ROLES, default engineer), title, avatarUrl,
team → Team, manager → User, seniority (intern|junior|mid|senior|staff|principal, default mid),
timezone, weeklyCapacityHours, compensation ({ annualSalary, currency, … }, scoped read),
isActive, lastLoginAt, refreshTokens[] ({ tokenId, … }, select:false).
| Method | Path | Min role | Notes |
|---|---|---|---|
| GET | /users |
any | filters: role, team, isActive, seniority |
| GET | /users/:id |
any | |
| GET | /users/:id/compensation |
leadership/self | scoped salary read |
| POST | /users |
engineering_manager | |
| PATCH | /users/:id |
engineering_manager | |
| PUT | /users/:id (compensation set) |
head_of_engineering | |
| POST | /users/:id/deactivate |
head_of_engineering | |
| DELETE | /users/:id |
admin |
Mounted at /teams. Model Team (embeds pto).
Fields: name, slug, description, lead → User, mission,
signals ({ … } qualitative inputs feeding health), weeklyCapacityHours,
pto[] ({ user, startDate, endDate, type, note }), tags[], isActive.
Derived (service): healthScore + healthBand, and capacity (committed
vs available accounting for PTO).
| Method | Path | Min role |
|---|---|---|
| GET | /teams · /teams/:id · /teams/:id/capacity |
any |
| POST | /teams |
engineering_manager |
| PATCH | /teams/:id |
engineering_manager |
| PUT | /teams/:id/members { members: [id] } |
engineering_manager |
| POST | /teams/:id/pto { user, startDate, endDate, type } |
engineering_manager |
| DELETE | /teams/:id/pto/:ptoId |
engineering_manager |
| DELETE | /teams/:id |
head_of_engineering |
Mounted at /projects. Model Project (embeds milestones).
Fields: name, key, description, status (discovery|planned|active|on_hold|completed|cancelled),
priority (low|medium|high|critical), investmentCategory (new_value|ktlo),
team → Team, owner → User, startDate, targetDate, progress,
roadmapHealth (on_track|at_risk|off_track), riskNotes,
milestones[] ({ title, status: planned|in_progress|done|blocked, dueDate }),
tags[], source.
CRUD. Filters: status, priority, team, owner, roadmapHealth. Write ≥ EM.
Mounted at /incidents. Model Incident (embeds timeline, postmortem).
Fields: title, description, severity (SEV1..SEV4),
status (open|investigating|identified|monitoring|mitigated|resolved),
service, team → Team, project → Project, commander → User,
detectedAt, resolvedAt, mttrMinutes (derived on resolve), affectedUsers,
tags[], timeline[] ({ at, type, message, author }),
postmortem ({ summary, rootCause, impact, resolution, lessonsLearned, publishedAt }),
source.
| Method | Path | Notes |
|---|---|---|
| GET | /incidents |
filters: severity, status, team, service, project |
| GET | /incidents/:id |
populated commander/team |
| POST | /incidents |
seeds an initial timeline entry |
| PATCH | /incidents/:id |
status=resolved stamps resolvedAt + computes mttrMinutes |
| POST | /incidents/:id/timeline { message, type } |
append timeline entry |
| PUT | /incidents/:id/postmortem { summary, rootCause, …, publish? } |
RCA/postmortem |
| DELETE | /incidents/:id |
Mounted at /tech-debt. Model TechnicalDebt.
Fields: title, description, category, status, team → Team,
component, owner → User, impactScore, riskScore, effortScore,
priorityScore (derived), quadrant (derived), tags[].
Derived (hook): priorityScore and quadrant from impact/risk/effort.
CRUD + GET /tech-debt/matrix?team= (prioritization quadrants).
Mounted at /architecture. Model ArchitectureComponent.
Fields: name, type (service/API/DB/…), description,
lifecycle, tier, repoUrl, docsUrl, runbookUrl, language,
ownerTeam → Team, ownerUser → User, apiSpec, dbSpec,
dependencies[] (→ other components), tags[].
CRUD + GET /architecture/graph (nodes + dependency edges). Filters: type,
lifecycle, tier, ownerTeam.
Mounted at /one-on-ones. Model OneOnOne.
Fields: manager → User, report → User, date, notes,
privateNotes (scoped), mood, feedback, careerGrowth
({ currentLevel, targetLevel, plan }),
goals[] ({ title, description, category, status, dueDate }), nextMeetingDate.
CRUD (write ≥ EM). Visibility scoped: managers see their own; leadership sees
all; privateNotes only returned to the owning manager / leadership.
Model MetricSnapshot — no HTTP routes of its own; written by the GitHub
integration (and seed), read by dashboard, okrs/forecast, and ai.
Fields: team → Team, date, leadTimeHours, deploymentCount,
deploymentFrequency, changeFailureRate, incidentCount, mttrMinutes,
availableCapacityHours, committedCapacityHours, source.
This is the integration-fed data class — see integrations.
Mounted at /dashboard. Pure aggregation, no own model.
| Method | Path | Returns |
|---|---|---|
| GET | /dashboard/summary?team= |
executive summary cards |
| GET | /dashboard/trends?team=&days=90 |
lead time / deploys / incidents / capacity series |
Mounted at /ai. OpenAI insights with heuristic fallback.
| Method | Path | Min role |
|---|---|---|
| GET | /ai/status |
any |
| GET | /ai/weekly-summary |
engineering_manager |
| GET | /ai/team-risk/:teamId |
engineering_manager |
| GET | /ai/burnout |
engineering_manager |
| GET | /ai/tech-debt |
engineering_manager |
| GET | /ai/roadmap-risk |
head_of_engineering |
| GET | /ai/health-report |
engineering_manager |
Each returns { content, model, source: "openai" | "fallback", ...context }.
Mounted at /okrs. Model Objective (embeds keyResults).
Objective fields: title, description, owner → User, team → Team,
quarter (e.g. 2026-Q2), level (company|team),
status (derived: on_track|at_risk|off_track|achieved), keyResults[], tags[].
KeyResult fields: title, metricType (percent|number|currency|boolean),
startValue, targetValue, currentValue, confidence (0–100).
Derived (okr.service, pure & unit-tested):
computeKeyResultProgress(kr)→ 0–100 % (boolean → 0/100; else clamped(current−start)/(target−start)).rollupObjective(krs)→{ progress (avg of KR progress), confidence (avg), status }.- Status is confidence-driven:
progress≥100 → achieved; elseconfidence<45 → off_track;<70 → at_risk; elseon_track.
| Method | Path | Notes |
|---|---|---|
| GET | /okrs |
list (filters: team, quarter, level, status, owner); each decorated with progress/confidence/status |
| GET | /okrs/:id |
populated owner/team/linkedProjects |
| GET | /okrs/rollup?quarter= |
overall + company + by-team summaries |
| GET | /okrs/forecast · /okrs/forecast/:projectId |
Monte Carlo delivery forecast (P50/P85, on-time prob.) |
| POST | /okrs |
create objective |
| PATCH | /okrs/:id |
update objective |
| DELETE | /okrs/:id |
|
| POST | /okrs/:id/key-results |
add KR |
| PATCH | /okrs/:id/key-results/:krId |
update KR |
Mounted at /org. Models Position (embeds pipeline candidates) and Skill.
Position fields: title, team → Team, seniority,
status (planned|open|interviewing|offer|filled|frozen), budgetedMonthlyCost,
openedAt, targetStartDate, filledAt, filledBy → User,
pipeline[] (candidates: { name, stage: applied|screen|onsite|offer|hired|rejected, appliedAt, note }),
notes.
Skill fields (per-person assessment): user → User, skill, category
(language|framework|platform|domain|soft|tooling), level (1–5), interest (1–5).
Unique index on (user, skill).
SkillCatalog fields (org-wide skill definition, model SkillCatalog):
name (unique), category (same enum), description?. Skill names on
assessments are chosen from this catalog (with quick-add on the Skills Matrix
form); the catalog itself is managed on its own page (/org/skill-catalog). It
exists to avoid duplicate/typo'd names (e.g. Node vs Node.js).
| Method | Path | Min role |
|---|---|---|
| GET | /org/headcount |
any (plan vs actual by team) |
| GET | /org/chart |
any (org tree, span of control) |
| GET | /org/attrition-risk |
any (per-person flight risk) |
| GET | /org/positions · /org/positions/funnel |
any |
| POST | /org/positions |
engineering_manager |
| PATCH | /org/positions/:id |
engineering_manager |
| DELETE | /org/positions/:id |
head_of_engineering |
| POST | /org/positions/:id/candidates |
engineering_manager (append to pipeline) |
| GET | /org/skills · /org/skills/matrix |
any |
| POST | /org/skills |
engineering_manager |
| PATCH | /org/skills/:id |
engineering_manager |
| DELETE | /org/skills/:id |
engineering_manager |
| GET | /org/skill-catalog |
any (org-wide skill definitions) |
| POST | /org/skill-catalog |
engineering_manager |
| PATCH | /org/skill-catalog/:id |
engineering_manager |
| DELETE | /org/skill-catalog/:id |
head_of_engineering |
skills/matrix aggregates per-skill people, experts, avgLevel, and
bus-factor risk (≤1 expert at level ≥ 4).
Attrition-risk formula (computeAttritionRisk, pure & unit-tested). The
per-person score is 0–100, summed from these conditions, then banded
(high ≥ 50 · medium ≥ 25 · low < 25):
| Condition | Points | Factor surfaced |
|---|---|---|
team attrition ≥ 20 |
+35 | High team attrition signal |
team attrition 12–19 |
+18 | — |
team morale < 55 |
+25 | Low team morale |
team morale 55–69 |
+10 | — |
team onCallLoad ≥ 60 |
+15 | Heavy on-call load |
| tenure 12–24 months | +15 | In 12-24mo flight-risk window |
| tenure > 48 months | +8 | Long tenure plateau |
| seniority senior/staff/principal | +7 | — |
Team signals (attrition, morale, onCallLoad) live on Team.signals and
default to 10 / 70 / 30 when absent; tenure is derived from User.createdAt.
The Retention page renders this same table in a collapsible "How is this
calculated?" card (org.method.* i18n keys) — keep it in sync with this list.
Mounted at /investment. Capacity allocation analytics, no own model — derives
from active projects' investmentCategory, tech-debt cost and incident cost.
| Method | Path | Returns |
|---|---|---|
| GET | /investment/allocation |
spend across `new_value |
| GET | /investment/trend |
allocation over time |
Mounted at /brief. Derived "what needs your attention this week" digest, no
own model — aggregates across incidents, OKRs, delivery, finance and people
signals.
| Method | Path | Returns |
|---|---|---|
| GET | /brief/weekly |
prioritized weekly brief items |
Mounted at /scorecard. Derived composite engineering-health grade with a
target and trend, no own model — rolls up delivery/reliability/people signals.
| Method | Path | Returns |
|---|---|---|
| GET | /scorecard |
composite score, target, trend, contributing dimensions |
Mounted at /engagement. Team engagement / eNPS via anonymous pulse surveys.
Model EngagementResponse.
Fields: team → Team, period, recommendScore (eNPS 0–10),
dimensions ({ … } per-dimension scores), comment.
| Method | Path | Returns / Body |
|---|---|---|
| GET | /engagement/summary |
eNPS, dimension averages, trend (derived) |
| POST | /engagement/responses |
submit an (anonymous) pulse response — source data entry |
Mounted at /leadership (auth + authorizeAtLeast('engineering_manager') on
the whole router; delete ≥ head_of_engineering). The cadence layer that turns
weekly reviews and syncs into tracked artifacts. A single model
LeadershipItem with a kind discriminator (decision | risk | action |
blocker) so the Brief and the Cadence dashboard can roll all four up uniformly.
Shared fields: kind, title, description, status (per-kind set),
priority (low|medium|high|critical), owner → User, team → Team,
project → Project, dueDate, resolvedAt, tags.
Kind-specific: probability & impact (1–5, risks) → derived
exposure = probability × impact; rationale & decidedOn (decisions);
blocks (blockers).
Per-kind status values — decision: proposed|accepted|rejected|superseded;
risk: open|mitigating|closed|accepted; action: todo|in_progress|done|cancelled;
blocker: open|in_progress|resolved.
| Method | Path | Returns / Body |
|---|---|---|
| GET | /leadership |
paginated list; filter by kind, status, priority, owner, team, project |
| GET | /leadership/summary |
counts by kind, open risks/blockers/actions, overdue, pending decisions, top risks by exposure, oldest blockers (derived; feeds the Brief) |
| GET | /leadership/:id |
one item (owner/team/project populated) |
| POST | /leadership |
create — source data entry |
| PATCH | /leadership/:id |
update (recomputes exposure on probability/impact change) |
| DELETE | /leadership/:id |
delete (≥ head_of_engineering) |
Mounted at /stakeholders (auth + authorizeAtLeast('engineering_manager');
delete ≥ head_of_engineering). Two models. Stakeholder — the relationship map
(name, organization, role, relationship, influence, sentiment,
owner, team, lastContactAt). Commitment — a promise/SLA/expectation
(title, stakeholder → Stakeholder, type = sla|commitment|expectation,
dueDate, status = on_track|at_risk|met|missed|cancelled, priority, owner,
team, project). Deleting a stakeholder cascades to its commitments.
| Method | Path | Returns / Body |
|---|---|---|
| GET | /stakeholders/summary |
sentiment counts, total, at-risk/missed commitments, upcoming (≤14d), by-status (derived) |
| GET · POST | /stakeholders · /stakeholders/:id (PATCH/DELETE) |
stakeholder CRUD — source data entry |
| GET · POST | /stakeholders/commitments · /stakeholders/commitments/:id (PATCH/DELETE) |
commitment/SLA CRUD — source data entry |
Mounted at /talent (auth + authorizeAtLeast('head_of_engineering') — talent
data is sensitive). Model TalentAssessment: user, period (unique together),
performance/potential (low|medium|high) → derived category (the 9-box:
star/high_performer/workhorse/high_potential/core/effective/enigma/inconsistent/risk),
flightRisk, promotionReadiness, isCriticalRole, successor → User,
successionReadiness.
| Method | Path | Returns / Body |
|---|---|---|
| GET | /talent |
paginated assessments; filter by category/performance/potential/flightRisk/isCriticalRole/team/user |
| GET | /talent/grid |
the 9-box grid: people bucketed by category |
| GET | /talent/summary |
stars, critical roles w/o successor, ready-to-promote, high flight risk (derived) |
| GET · POST · PATCH · DELETE | /talent · /talent/:id |
assessment CRUD — source data entry |
Mounted at /vendors (auth + authorizeAtLeast('engineering_manager') read;
writes ≥ head_of_engineering — spend is finance-adjacent). Model Vendor:
name, category, status, services, monthlyCost, renewalDate,
autoRenews, performanceRating (1–5), compliance, owner.
| Method | Path | Returns / Body |
|---|---|---|
| GET | /vendors/summary |
active count, monthly/annual spend, by-category, upcoming renewals (≤60d), compliance issues (derived) |
| GET · POST · PATCH · DELETE | /vendors · /vendors/:id |
vendor CRUD — source data entry |
Mounted at /devex (auth; write ≥ engineer, delete ≥ engineering_manager).
Model FrictionItem: title, area (ci_cd|environments|tooling|dependencies|
process|local_dev|docs|other), status, frequency, hoursLostPerWeek (per
person) & affectedPeople → derived weeklyHoursLost = hoursLostPerWeek × affectedPeople, team, owner.
| Method | Path | Returns / Body |
|---|---|---|
| GET | /devex |
paginated frictions, sorted by weekly hours lost; filter by area/status/team/owner |
| GET | /devex/summary |
open count, total weekly hours lost, by-area, top frictions, resolved (derived) |
| GET · POST · PATCH · DELETE | /devex · /devex/:id |
friction CRUD — source data entry |
Mounted at /finance (auth + authorizeAtLeast('engineering_manager') on the
whole router). Reads ≥ engineering_manager; writes ≥ head_of_engineering.
| Method | Path | Returns |
|---|---|---|
| GET | /finance/dashboard/executive |
category totals, monthly trend, by team, by product |
| GET | /finance/dashboard/cloud |
by provider, trend, >20% MoM growth alerts |
| GET | /finance/dashboard/tools |
utilization, wasted spend, underused tools |
| GET | /finance/dashboard/teams |
by team, cost/engineer, payroll from real salaries, trend |
| GET | /finance/dashboard/products |
cost, revenue, margin, profitability index |
| GET | /finance/dashboard/tech-debt |
top-20 most expensive, by team, totals |
| GET | /finance/dashboard/incidents |
by severity, by team, trend |
| GET | /finance/dashboard/cost-of-delay |
top delayed initiatives, revenue at risk |
| GET | /finance/dashboard/hiring-roi |
cost vs not-hiring comparison |
| Method | Path | Returns |
|---|---|---|
| GET | /finance/advisor/recommendations |
savings, risks, executive recommendations |
| GET | /finance/advisor/weekly-report |
weekly executive cost report |
Each registered via mountCrud(): GET (list, paginated/filtered/sorted/
searchable), GET /:id, POST, PATCH /:id, DELETE /:id. Write ≥ Head of
Engineering. Fields and formulas in the next section.
/finance/engineering-costs · /cloud-costs · /tool-costs · /team-costs ·
/product-costs · /tech-debt-costs · /incident-costs · /cost-of-delay ·
/hiring-roi.
Mounted at /integrations (auth required). Models Integration and SyncRun.
Integration fields: provider (github|jira|pagerduty|cloud, unique),
status (connected|disconnected|error), mode (dummy|live),
config (Mixed — org/repos, host/projectKeys; tokens encrypted in live),
cursor, lastSyncAt, lastError.
SyncRun fields: provider, status, mode, created, updated,
durationMs, error.
| Method | Path | Min role | Notes |
|---|---|---|---|
| GET | /integrations |
engineering_manager | list provider states |
| GET | /integrations/:provider/runs |
engineering_manager | recent sync runs |
| PATCH | /integrations/:provider |
head_of_engineering | update config/mode |
| POST | /integrations/:provider/sync |
head_of_engineering | run a sync |
Provider → connector → model mapping (current):
github→MetricSnapshot(DORA: deploys, lead time, change failure rate).jira,pagerduty,cloud→ wired connectors (issues/incidents/cloud cost), indummymode by default.
Connectors persist via upsertBySource() (idempotent on a source key). In
dummy mode providers return deterministic sample data (no network); live
mode would call the real API using stored config. See
INTEGRATIONS.md.
All amounts default to USD (currency field). FK references are not
populated on the generic list endpoints (the UI resolves names from its own
lists).
| Resource | Path | Editable fields | Derived (server-side) |
|---|---|---|---|
| Engineering cost | /finance/engineering-costs |
month, year, payrollCost, infrastructureCost, saasToolsCost, contractorsCost, currency |
totalCost = payroll + infrastructure + saasTools + contractors |
| Cloud cost | /finance/cloud-costs |
provider, service, month, year, amount, currency, team?, product?, notes? |
— (source set by integrations) |
| Tool cost | /finance/tool-costs |
toolName, category, monthlyCost, activeLicenses, usedLicenses, renewalDate?, owner?, notes? |
utilization = used/active×100, wastedMonthlySpend = (active−used)×(monthlyCost/active) |
| Team cost | /finance/team-costs |
team, month, year, payrollCost?, infrastructureAllocation?, toolingAllocation?, contractorCost?, headcount? |
totalCost = sum(allocations), costPerEngineer = totalCost/headcount |
| Product cost | /finance/product-costs |
product, month, year, payrollAllocation?, infrastructureAllocation?, toolingAllocation?, monthlyRevenue? |
totalCost, grossMargin = revenue−totalCost, profitabilityIndex = revenue/totalCost |
| Tech-debt cost | /finance/tech-debt-costs |
technicalDebt, team?, product?, hoursLostPerMonth, averageHourlyRate, impactLevel? |
estimatedMonthlyCost = hoursLostPerMonth × averageHourlyRate |
| Incident cost | /finance/incident-costs |
incident, team?, severity?, engineersInvolved, durationHours, estimatedHourlyRate, customerImpactScore? |
estimatedCost = engineersInvolved × durationHours × estimatedHourlyRate |
| Cost of delay | /finance/cost-of-delay |
featureName, product?, team?, expectedMonthlyRevenue, delayMonths, status?, notes? |
estimatedCostOfDelay = expectedMonthlyRevenue × delayMonths |
| Hiring ROI | /finance/hiring-roi |
role, team?, seniority?, annualCost, estimatedProductivityGain?, estimatedRevenueImpact?, status?, notes? |
estimatedROI = (estimatedRevenueImpact − annualCost)/annualCost × 100 |
Enum reference:
- ToolCost
category:communication|source_control|observability|design|project_mgmt|docs|security|other - TechDebtCost
impactLevel:low|medium|high|critical - IncidentCost
severity:SEV1|SEV2|SEV3|SEV4 - CostOfDelay
status:at_risk|delayed|shipped|cancelled - HiringROI
seniority:junior|mid|senior|staff|principal;status:proposed|approved|hired|rejected
The dashboard/teams endpoint additionally merges real payroll from each
active member's compensation.annualSalary (aggregation bypasses the
select:false flag server-side), giving actualHeadcount, actualAnnualPayroll
and costPerPerson alongside the ledger figures.
SPA routes (web/src/App.tsx), all behind ProtectedRoute → AppLayout, each a
lazy chunk. "Entry" = whether the screen can create/edit its source data.
| Route | Page | Primary data source | Entry UI |
|---|---|---|---|
/dashboard |
Dashboard | dashboard/summary+trends (derived) |
— derived |
/brief |
Brief | brief/weekly (derived) |
— derived |
/scorecard |
Scorecard | scorecard (derived) |
— derived |
/cadence |
Cadence | /leadership (+/leadership/summary) |
✅ decisions, risks, actions, blockers |
/stakeholders |
Stakeholders | /stakeholders (+/commitments, /summary) |
✅ stakeholders, commitments/SLAs |
/org/talent |
Talent | /talent (+/grid, /summary) |
✅ talent assessments (9-box, succession) |
/vendors |
Vendors | /vendors (+/summary) |
✅ vendors/contracts |
/devex |
DevEx | /devex (+/summary) |
✅ friction items |
/teams |
Teams | /teams (+capacity, members, PTO) |
✅ teams, members, PTO |
/users |
Users | /users (+compensation) |
✅ users, compensation |
/projects |
Projects | /projects |
✅ projects, milestones |
/incidents |
Incidents | /incidents |
✅ incidents, timeline, postmortem |
/tech-debt |
TechDebt | /tech-debt (+matrix) |
✅ debt items |
/architecture |
Architecture | /architecture (+graph) |
✅ components |
/one-on-ones |
OneOnOnes | /one-on-ones |
✅ 1:1s, goals |
/ai-insights |
AIInsights | /ai/* (derived) |
— derived (route hidden from sidebar) |
/integrations |
Integrations | /integrations (+runs) |
✅ config, sync |
/settings |
Settings | /auth/me |
✅ account |
/org |
PeopleDashboard | /org/headcount,chart (derived) |
— derived |
/org/headcount |
Headcount | /org/headcount,positions,funnel |
✅ positions, candidates |
/org/skills |
SkillsMatrix | /org/skills/matrix,/org/skills,/org/skill-catalog |
✅ skill assessments (skill picked from catalog) |
/org/skill-catalog |
SkillCatalog | /org/skill-catalog |
✅ skill definitions |
/org/retention |
Retention | /org/attrition-risk (derived) |
— derived |
/org/engagement |
Engagement | engagement/summary |
✅ pulse responses (eNPS) |
/okrs |
OKRsBoard | /okrs,/okrs/rollup |
✅ objectives, KRs |
/okrs/forecast |
DeliveryForecast | /okrs/forecast (derived) |
— derived |
/investment |
Investment | /investment/* (derived) |
— derived |
/finance |
FinanceDashboard | dashboard/executive |
✅ engineering costs |
/finance/cloud |
CloudCosts | dashboard/cloud+cloud-costs |
✅ cloud costs |
/finance/saas |
SaaSCosts | dashboard/tools+tool-costs |
✅ tool costs |
/finance/teams |
TeamCosts | dashboard/teams |
✅ team costs |
/finance/products |
ProductCosts | dashboard/products |
✅ product costs |
/finance/tech-debt |
TechDebtCosts | dashboard/tech-debt |
✅ tech-debt costs |
/finance/incidents |
IncidentCosts | dashboard/incidents |
✅ incident costs |
/finance/cost-of-delay |
CostOfDelay | dashboard/cost-of-delay |
✅ cost of delay |
/finance/hiring-roi |
HiringROI | dashboard/hiring-roi |
✅ hiring ROI |
/finance/advisor |
CostAdvisor | advisor/recommendations (derived) |
— derived |
/finance/reports |
ExecutiveReports | advisor/weekly-report (derived) |
— derived |
(Bold "Entry" items were the data-entry gaps closed most recently — see the changelog.)
components/finance/FinanceLedger.tsx— declarative cost-ledger UI (paginated table + create/edit dialog + delete), parameterized byfields,columns,empty,toForm,rowLabel,invalidateKeys. Used by all seven finance ledger pages so they stay consistent and analytics dashboards refresh on edit.components/shared/*—PageHeader,StatCard,States(Loading/Error/Empty),Pagination,RowActions+ConfirmDelete+useRowDelete,IntegrationNotice(banner telling the user which integration feeds/augments a screen, e.g.cloudon the finance dashboard,pagerdutyon incident costs),SourceNotice(sibling banner for data that is entered on a different screen rather than ingested — states where it comes from and links to the owning screen, e.g. team signals on Retention link to/teams).components/ui/*— Button, Input/Textarea/Label/Select, Dialog, Table, Card, Badge (Tailwind + CVA primitives).api/*hooks — React Query wrappers:hooks.ts(users/teams/projects/ incidents/tech-debt/architecture/1:1s/AI/compensation),okrs.ts,org.ts,finance.ts,integrations.ts,investment.ts,leadership.ts(brief/scorecard/engagement + cadence CRUD),stakeholders.ts,talent.ts,vendors.ts,devex.ts.lib/api.ts— axios instance with Bearer injection,apiError(), and refresh-and-retry on 401.lib/permissions.ts—useCan(role).
The product rule (see ARCHITECTURE §6): every displayed value is enterable (form) or ingested (integration); derived views are exempt because their inputs are enterable.
| Data | Origin | Where entered |
|---|---|---|
| Users, teams, projects, incidents, tech debt, architecture, 1:1s | manual | their pages |
| OKRs (objectives, KRs) | manual | /okrs |
| Positions & hiring candidates | manual | /org/headcount |
| Skill catalog (org-wide skill definitions) | manual | /org/skill-catalog |
| Skill assessments (per user, level/interest) | manual | /org/skills (skill name from the catalog) |
| All 9 finance cost ledgers | manual | their /finance/* pages |
| Engagement pulse responses (eNPS) | manual | /org/engagement |
| Cadence items (decisions, risks, actions, blockers) | manual | /cadence |
| Stakeholders & commitments/SLAs | manual | /stakeholders |
| Talent assessments (9-box, succession) | manual | /org/talent |
| Vendors / contracts | manual | /vendors |
| DevEx friction items | manual | /devex |
| DORA metric snapshots (deploys, lead time, CFR, MTTR) | integration | GitHub sync (/integrations) |
| Dashboards, roll-ups, forecasts, brief, scorecard, AI insights, advisor | derived | n/a (computed) |
- Library:
i18next+react-i18next; init inweb/src/i18n/. - Locales:
web/src/i18n/locales/es.tsanden.ts, sharing one key tree. - Namespaces:
brand,nav,common,theme,language,auth,roles,pages, and per-domain (finance,skills,org,okrs,projects,incidents,techDebt, …). - Components use
const { t } = useTranslation()andt('namespace.key'), with{{interpolation}}anddefaultValuefallbacks where helpful. - Rule: every key must exist in both locales. Seed/demo content is Spanish.
Two locale variants of the same dataset coexist; each wipes and repopulates the same collections, so run whichever language you want to demo:
| Script | File | Content |
|---|---|---|
npm run seed -w server |
server/src/seed.ts |
demo data in Spanish |
npm run seed:en -w server |
server/src/seed.en.ts |
same shape, content in English |
Both seed an org of teams/users, projects, incidents, tech debt, architecture,
OKRs (2026-Q2), 1:1s, positions, the skill catalog (TypeScript, Go, React, …),
skill assessments and 90 days of MetricSnapshot
history, then call seedFinance(...) (in finance.seed.ts) for every finance
cost ledger. The two scripts are intentionally structural mirrors — only the
human-readable strings differ (titles, missions, descriptions, project/incident
names, OKRs, 1:1 notes, skill names, finance feature/role labels). Person names,
emails, numbers, dates and signal values are identical, so derived analytics
(attrition risk, finance roll-ups, forecasts) look the same in either locale.
finance.seed.ts takes a locale: 'es' | 'en' arg (defaults to 'es') to swap
its few labels (cloud service name, cost-of-delay features, hiring-ROI roles).
Demo login (both): admin@nova.dev / Password123! (CTO).
Because of the "everything is enterable" rule, the seed is a convenience for demos/tests — every seeded entity also has a UI + endpoint to create it.
npm run test -w server # jest --runInBand- Hermetic integration tests use
mongodb-memory-server. - Coverage: auth flow & refresh rotation, incident lifecycle/MTTR, team health score, tech-debt score, OKR roll-up, org skills aggregation.
server/src/tests/auth.integration.spec.ts
server/src/tests/incidents.integration.spec.ts
server/src/modules/teams/team.service.spec.ts
server/src/modules/techDebt/techDebt.model.spec.ts
server/src/modules/okrs/okr.service.spec.ts
server/src/modules/org/org.service.spec.ts
Frontend validation: npm run typecheck -w web and npm run build -w web.
Update this document in the same change that alters the code. Specifically:
add/modify a model field → update its module section and (if exposed) the
finance table / frontend table;
add/modify an endpoint → update the module table and API.md;
add a page → update the frontend reference and
data provenance matrix.
- 2026-07-04 — Refreshed the README for the leadership + Phase-2 modules
(monorepo tree, api-hooks list, sensitive-data RBAC table, test-file list) and
added
docs/HOE_PLAYBOOK.md— a workflow playbook mapping the Head-of-Engineering Monday-to-Friday routine to the exact Nova screens. - 2026-07-03 — Added four management modules (Phase 2) to close the
Head-of-Engineering weekly-routine gaps: Stakeholders & Commitments/SLAs
(
/stakeholders,Stakeholder+Commitmentmodels, page with two tabs), Talent Review (/talent,TalentAssessmentwith derived 9-boxcategory, succession signals, grid + list views; HoE-only), Vendors & Contracts (/vendors,Vendormodel with spend/renewal/compliance roll-up), and Engineering Productivity / DevEx (/devex,FrictionItemwith derivedweeklyHoursLost). Each: full CRUD + summary endpoint, RBAC,/apimount, a dedicated page + api hooks, sidebar entry, bilingual i18n, seed data in both locales, and integration tests (derivations + RBAC). Documented models/endpoints/provenance/frontend tables. - 2026-07-03 — Added the Leadership Cadence module — the operating layer
that turns weekly reviews/syncs into tracked artifacts. New
LeadershipItemmodel (single collection,kind= decision/risk/action/blocker) with CRUD + summary under/leadership(read ≥ EM, delete ≥ HoE); risks deriveexposure = probability × impact. New/cadencepage with per-kind tabs and a summary strip (Cadence.tsx), sidebar entry (nav.cadence), and cadence CRUD hooks inapi/leadership.ts. The Weekly Brief now folds in acadencedomain (open blockers, overdue follow-ups, high-exposure risks, pending decisions). Seed data in both locales; new i18n keys (nav.cadence,pages.cadence,cadence.*,brief.domain.cadence). Integration tests cover exposure derivation, summary roll-up and RBAC. Documented model/endpoints/provenance/frontend tables. - 2026-06-07 — Added a Skill Catalog: a new
SkillCatalogmodel (org-wide skill definitions: name/category/description) with CRUD under/org/skill-catalog, a dedicated sidebar page (/org/skill-catalog,SkillCatalog.tsx) and seed entries in both locales. The Skills Matrix assessment form now picks the skill from this catalog (with quick-add for new ones) and carries aSourceNoticelinking to the catalog. New i18n keys (nav.skillCatalog,pages.skillCatalog,skillCatalog.*,sourceNotice.skills,skills.newSkill*). Documented model/endpoints/provenance/frontend tables. - 2026-06-07 — Added an English seed variant (
server/src/seed.en.ts, run withnpm run seed:en) that coexists with the Spanishseed.ts— a structural mirror with English content. Parameterizedfinance.seed.tswith alocalearg so its few labels follow suit. Documented both under Seed data; added the script toREADME.md. - 2026-06-07 — Added
docs/USER_MANUAL.md: an end-user, screen-by-screen guide (English, no screenshots) covering every screen — purpose, location, what you see, what you can do, RBAC and data source — grouped by the sidebar. Linked fromREADME.mdand added to the docs-maintenance table. Also relabeled the Skills Matrix entry point from "Add assessment / Skill assessments" to "Add skill / Skills by person" for discoverability (i18nskills.addAssessment/skills.assessments, es + en). - 2026-06-07 — Added the
SourceNoticebanner for cross-screen data: any screen that displays values entered on a different screen shows an info message naming the source and linking to the owning screen(s) (sibling ofIntegrationNotice, which covers ingested data). The component takes amessage+ alinks[]array. Applied to the derived screens: Retention (→ Teams), People & Org (→ Users, Teams), Delivery Forecast (→ Projects), Engineering Investment (→ Projects, Tech Debt, Incidents) and the Finance Dashboard (→ Team/Product/SaaS Costs). Messages live under thesourceNotice.*i18n namespace (es + en); link labels reusenav.*. - 2026-06-07 — Documented newly added modules/screens:
brief(/brief),scorecard(/scorecard) andengagement(/org/engagement, with pulse-survey entry). Added a frontend navigation sitemap diagram toARCHITECTURE.md(§11) and theIntegrationNoticeper-screen banner. AI Insights route hidden from the sidebar (still routable). - 2026-06-07 — Retention page now explains itself: added a collapsible
"How is this calculated?" card surfacing the attrition-risk inputs, the full
scoring-rule table and the band thresholds, mirroring
computeAttritionRisk. Neworg.method.*i18n keys in es + en; documented the formula here. - 2026-06-07 — Closed the manual-entry gaps so all source data is enterable:
added the reusable
FinanceLedgerand create/edit/delete UIs for the 7 finance ledgers that previously had backend CRUD but no form (engineering, team, product, tech-debt, incident costs, cost of delay, hiring ROI); added skill-assessment management on the Skills Matrix; added hiring-candidate entry on Headcount. New i18n keys added in es + en. Authored README / ARCHITECTURE / DETAIL as the living documentation set.