ceo-api/src/navigation/menu-registry.ts
valentinbvro 42cefaddb2 feat: Platform Kernel v1 -- workspaces, ExecutionContext, /v1/navigation, event envelope
Specul de arhitectura cere Platform Kernel INAINTEA modulelor de domeniu.
Modulele A1/A2 au fost construite peste un kernel caruia ii lipseau exact
piesele astea. Le adaug acum, aditiv, fara sa rup ce merge.

- workspaces: tenant != workspace. Workspace-ul e contextul de lucru DIN
  tenant. Backfill: fiecare tenant existent primeste workspace implicit,
  altfel SessionGuard i-ar respinge toate requesturile.
- memberships.workspace_id + valid_from/valid_until: rol per workspace si
  acces delegat cu expirare (contabil pana la o data). SessionGuard respinge
  membership expirat si membership legat de alt workspace.
- ExecutionContext inlocuieste sesiunea subtire (userId+tenantId+role):
  requestId, correlationId, workspaceId, membershipId, roles, permissions,
  purpose, timezone, source. Tipul vechi ramane exportat sub acelasi nume,
  ca sa nu ating ~15 module de domeniu doar pentru o redenumire.
- GET /v1/navigation: menu registry mutat in backend. Filtreaza pe rol, tip
  de workspace, permisiuni si feature flags; intoarce doar itemii autorizati.
  Ramane UX, nu securitate -- fiecare endpoint verifica din nou.
- event envelope: workspace_id, occurred_at, actor_id, aggregate_type,
  causation_id, classification, provenance
- audit envelope: workspace_id, actor_type, purpose, changed_fields,
  before/after hash, session_id
2026-07-29 11:46:20 +02:00

167 lines
6.9 KiB
TypeScript

import type { MembershipRole } from '../auth/execution-context';
/**
* MENU REGISTRY -- sursa de adevar pentru Main Navigator, in BACKEND (spec §5).
* Frontend-ul nu mai decide ce vede userul; primeste doar itemii autorizati de
* la /v1/navigation. Cea din ceo-web ramane doar ca fallback offline.
*
* releaseStage = CAND e planificat (A0..A4). status = daca exista ACUM in cod.
*/
export type ReleaseStage = 'A0' | 'A1' | 'A2' | 'A3' | 'A4';
export type MenuStatus = 'ready' | 'planned';
export type WorkspaceType = 'personal' | 'family' | 'business' | 'community' | 'project';
export interface MenuItem {
id: string;
label: string;
route: string;
icon: string;
parentId?: string;
order: number;
requiredPermissions: string[];
allowedWorkspaceTypes: WorkspaceType[];
allowedRoles?: MembershipRole[];
featureFlag?: string;
releaseStage: ReleaseStage;
status: MenuStatus;
isEnabled: boolean;
/** Ce lipseste ca sa devina 'ready'. Afisat in UI pe pagina neconstruita. */
missing?: string;
}
const ALL: WorkspaceType[] = ['personal', 'family', 'business', 'community', 'project'];
const B = '/dashboard';
/** Sectiunile (parinti). Copiii le refera prin parentId. */
export const MENU_SECTIONS: MenuItem[] = [
s('command-center', 'Command Center', 'command', 1, 'A1', ALL),
s('life-os', 'Life OS', 'life', 2, 'A3', ['personal', 'family']),
s('business-os', 'Business OS', 'business', 3, 'A1', ['business', 'project']),
s('documents', 'Documents & Evidence', 'documents', 4, 'A2', ALL),
s('intelligence', 'Intelligence', 'intelligence', 5, 'A2', ALL),
s('decisions', 'Decisions & Opportunities', 'decisions', 6, 'A3', ALL),
s('relationships', 'Relationships & Community', 'relationships', 7, 'A3', ALL),
s('trust', 'Trust & Reputation', 'trust', 8, 'A4', ALL),
s('ai', 'AI & Automations', 'ai', 9, 'A2', ALL),
s('reports', 'Reports', 'reports', 10, 'A2', ALL),
s('data-intelligence', 'Data Intelligence', 'data', 11, 'A2', ALL, ['owner', 'admin']),
s('integrations', 'Integrations & Devices', 'integrations', 12, 'A3', ALL, ['owner', 'admin']),
s('privacy', 'Privacy, Security & Audit', 'privacy', 13, 'A2', ALL),
s('settings', 'Settings & Administration', 'settings', 14, 'A1', ALL),
];
function s(
id: string,
label: string,
icon: string,
order: number,
releaseStage: ReleaseStage,
allowedWorkspaceTypes: WorkspaceType[],
allowedRoles?: MembershipRole[],
): MenuItem {
return {
id,
label,
route: '',
icon,
order,
requiredPermissions: [],
allowedWorkspaceTypes,
allowedRoles,
releaseStage,
status: 'planned',
isEnabled: true,
};
}
function item(
id: string,
parentId: string,
label: string,
route: string,
order: number,
releaseStage: ReleaseStage,
status: MenuStatus,
requiredPermissions: string[],
extra: Partial<MenuItem> = {},
): MenuItem {
return {
id,
parentId,
label,
route,
icon: '',
order,
requiredPermissions,
allowedWorkspaceTypes: ALL,
releaseStage,
status,
isEnabled: true,
...extra,
};
}
export const MENU_ITEMS: MenuItem[] = [
// 01 Command Center
item('cc-overview', 'command-center', 'Executive Overview', B, 1, 'A1', 'ready', ['navigation:read']),
item('cc-briefing', 'command-center', 'Daily Briefing', `${B}/briefing`, 2, 'A1', 'ready', ['briefing:read']),
item('cc-priorities', 'command-center', 'Priorities', `${B}/priorities`, 3, 'A2', 'planned', ['briefing:read'], {
missing: 'Priority Rules Engine determinist; AI doar explica scorul, nu il stabileste.',
}),
item('cc-alerts', 'command-center', 'Alerts & Deadlines', `${B}/alerts`, 4, 'A2', 'planned', ['briefing:read'], {
missing: 'Obiect comun Deadline alimentat din contracte, facturi, documente si aplicatii.',
}),
item('cc-approvals', 'command-center', 'Approval Center', `${B}/approvals`, 5, 'A2', 'planned', ['*'], {
missing: 'Coada de aprobare pentru clasele R3-R5. AI Gateway le blocheaza deja pana exista.',
}),
item('cc-activity', 'command-center', 'Activity Timeline', `${B}/activity`, 6, 'A2', 'planned', ['*'], {
missing: 'Vizualizare peste outbox_events + audit_log, care se populeaza deja.',
}),
item('cc-review', 'command-center', 'Daily / Weekly Review', `${B}/review`, 7, 'A2', 'planned', ['briefing:read'], {
missing: 'Job programat saptamanal + snapshot de review.',
}),
// 03 Business OS (itemii construiti)
item('bo-organizations', 'business-os', 'Organizations', `${B}/organizations`, 2, 'A1', 'ready', ['organization:read']),
item('bo-tasks', 'business-os', 'Tasks & Operations', `${B}/tasks`, 6, 'A1', 'ready', ['task:read']),
item('bo-transactions', 'business-os', 'Transactions', `${B}/transactions`, 8, 'A2', 'planned', ['*'], {
missing: 'Tabela transactions exista (cu evidence_status); lipsesc /v1/transactions si state machine-ul.',
}),
item('bo-accountant', 'business-os', 'Accountant Pack', `${B}/accountant-pack`, 13, 'A2', 'planned', ['*'], {
missing: 'Export pe perioada + verificare evidence gaps + partajare limitata in timp.',
}),
// 05 Intelligence (itemii construiti)
item('int-companies', 'intelligence', 'Company Intelligence', `${B}/companies`, 3, 'A2', 'ready', ['intelligence:search']),
item('int-segments', 'intelligence', 'Saved Segments', `${B}/segments`, 6, 'A2', 'ready', ['segment:read']),
item('int-research', 'intelligence', 'Research Briefs', `${B}/research`, 7, 'A2', 'ready', ['research_brief:read']),
item('int-memory', 'intelligence', 'Memory & Knowledge', `${B}/memory`, 11, 'A4', 'planned', ['*'], {
missing: 'memory-graph (Apache AGE) ruleaza pe server, neconectat la ceo-api.',
}),
// 09 AI & Automations
item('ai-assistant', 'ai', 'AI Executive Assistant', `${B}/assistant`, 1, 'A2', 'planned', ['*'], {
missing: 'AI Gateway exista si Hermes ruleaza izolat, dar ceo-api nu expune inca server MCP cu uneltele CEO OS.',
}),
item('ai-costs', 'ai', 'Costs & Usage', `${B}/ai/costs`, 9, 'A2', 'planned', ['*'], {
missing: 'Costurile reale se salveaza deja per cerere in ai_requests.cost_usd; lipseste agregarea.',
}),
// 11 Data Intelligence (doar owner/admin, mostenit de la sectiune)
item('di-overview', 'data-intelligence', 'Data Overview', `${B}/data`, 1, 'A2', 'planned', ['*'], {
missing: 'Platforma de date e functionala (Apollo/ClickHouse); lipseste UI-ul de administrare.',
}),
// 13 Privacy
item('pr-audit', 'privacy', 'Audit Log', `${B}/privacy/audit`, 6, 'A2', 'planned', ['*'], {
missing: 'audit_log se populeaza la fiecare operatiune materiala; lipseste UI-ul de consultare.',
}),
// 14 Settings
item('set-workspaces', 'settings', 'Workspaces', `${B}/settings/workspaces`, 3, 'A1', 'planned', ['*'], {
missing: 'Workspaces exista acum in backend; lipseste UI-ul de administrare si creare.',
}),
item('set-members', 'settings', 'Members', `${B}/members`, 4, 'A1', 'ready', ['*'], {
allowedRoles: ['owner', 'admin'],
}),
];