Minimal RBAC + Feature Flags + Developer Mode
- 4 roles: SuperAdmin, Operator, Reviewer, PilotUser - Capabilities (not pages): mission.create, artifact.view, decision.approve, etc. - Feature flags: ENABLE_REPLAY, ENABLE_MODEL_TRAINING, etc. - Developer Mode toggle in UI (activated by permission) - New rule: All features must link to module, capability, and role Next: Deploy pilot environment
This commit is contained in:
@@ -0,0 +1,88 @@
|
|||||||
|
/**
|
||||||
|
* Capabilities — minimal RBAC for Pilot 001-010
|
||||||
|
*
|
||||||
|
* Not pages — capabilities (what user CAN do).
|
||||||
|
* Roles are combinations of capabilities.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type Capability =
|
||||||
|
// Mission
|
||||||
|
| 'mission.create'
|
||||||
|
| 'mission.edit'
|
||||||
|
| 'mission.assign'
|
||||||
|
| 'mission.view'
|
||||||
|
// Artifact
|
||||||
|
| 'artifact.view'
|
||||||
|
| 'artifact.download'
|
||||||
|
| 'artifact.delete'
|
||||||
|
// Decision
|
||||||
|
| 'decision.review'
|
||||||
|
| 'decision.approve'
|
||||||
|
// Model (Developer Mode)
|
||||||
|
| 'model.train'
|
||||||
|
| 'model.deploy'
|
||||||
|
// Economy
|
||||||
|
| 'economy.view'
|
||||||
|
| 'economy.edit'
|
||||||
|
// Admin
|
||||||
|
| 'admin.users'
|
||||||
|
| 'admin.settings'
|
||||||
|
| 'admin.developerMode';
|
||||||
|
|
||||||
|
export interface Role {
|
||||||
|
readonly id: string;
|
||||||
|
readonly name: string;
|
||||||
|
readonly capabilities: Capability[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Roles: Record<string, Role> = {
|
||||||
|
superAdmin: {
|
||||||
|
id: 'super_admin',
|
||||||
|
name: 'Super Admin',
|
||||||
|
capabilities: [
|
||||||
|
'mission.create', 'mission.edit', 'mission.assign', 'mission.view',
|
||||||
|
'artifact.view', 'artifact.download', 'artifact.delete',
|
||||||
|
'decision.review', 'decision.approve',
|
||||||
|
'model.train', 'model.deploy',
|
||||||
|
'economy.view', 'economy.edit',
|
||||||
|
'admin.users', 'admin.settings', 'admin.developerMode',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
operator: {
|
||||||
|
id: 'operator',
|
||||||
|
name: 'Operator',
|
||||||
|
capabilities: [
|
||||||
|
'mission.create', 'mission.edit', 'mission.assign', 'mission.view',
|
||||||
|
'artifact.view', 'artifact.download',
|
||||||
|
'decision.review',
|
||||||
|
'model.train', 'model.deploy',
|
||||||
|
'economy.view',
|
||||||
|
'admin.developerMode',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
reviewer: {
|
||||||
|
id: 'reviewer',
|
||||||
|
name: 'Reviewer',
|
||||||
|
capabilities: [
|
||||||
|
'mission.view',
|
||||||
|
'artifact.view', 'artifact.download',
|
||||||
|
'decision.review', 'decision.approve',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
pilotUser: {
|
||||||
|
id: 'pilot_user',
|
||||||
|
name: 'Pilot User',
|
||||||
|
capabilities: [
|
||||||
|
'mission.create', 'mission.view',
|
||||||
|
'artifact.view',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function hasCapability(role: Role, capability: Capability): boolean {
|
||||||
|
return role.capabilities.includes(capability);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function hasAnyCapability(role: Role, capabilities: Capability[]): boolean {
|
||||||
|
return capabilities.some(c => hasCapability(role, c));
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
/**
|
||||||
|
* Feature Flags — enable features without new releases
|
||||||
|
*
|
||||||
|
* Pilot 001-010: Control which modules are visible.
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type FeatureFlag =
|
||||||
|
| 'ENABLE_REPLAY'
|
||||||
|
| 'ENABLE_DATASET_EXPLORER'
|
||||||
|
| 'ENABLE_MODEL_TRAINING'
|
||||||
|
| 'ENABLE_HOTSPOTS'
|
||||||
|
| 'ENABLE_ECONOMIC_ENGINE'
|
||||||
|
| 'ENABLE_DEVELOPER_MODE';
|
||||||
|
|
||||||
|
export interface FeatureFlags {
|
||||||
|
readonly flags: Record<FeatureFlag, boolean>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const DefaultFeatureFlags: FeatureFlags = {
|
||||||
|
flags: {
|
||||||
|
ENABLE_REPLAY: false,
|
||||||
|
ENABLE_DATASET_EXPLORER: true,
|
||||||
|
ENABLE_MODEL_TRAINING: false,
|
||||||
|
ENABLE_HOTSPOTS: false,
|
||||||
|
ENABLE_ECONOMIC_ENGINE: false,
|
||||||
|
ENABLE_DEVELOPER_MODE: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export function isEnabled(flags: FeatureFlags, flag: FeatureFlag): boolean {
|
||||||
|
return flags.flags[flag] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function enable(flags: FeatureFlags, flag: FeatureFlag): FeatureFlags {
|
||||||
|
return {
|
||||||
|
flags: {
|
||||||
|
...flags.flags,
|
||||||
|
[flag]: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export function disable(flags: FeatureFlags, flag: FeatureFlag): FeatureFlags {
|
||||||
|
return {
|
||||||
|
flags: {
|
||||||
|
...flags.flags,
|
||||||
|
[flag]: false,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -88,6 +88,24 @@ export {
|
|||||||
} from './mission/knowledge-gap';
|
} from './mission/knowledge-gap';
|
||||||
|
|
||||||
export * from './common/enums';
|
export * from './common/enums';
|
||||||
|
|
||||||
|
// Auth
|
||||||
|
export {
|
||||||
|
Capability,
|
||||||
|
Role,
|
||||||
|
Roles,
|
||||||
|
hasCapability,
|
||||||
|
hasAnyCapability,
|
||||||
|
} from './auth/capabilities';
|
||||||
|
|
||||||
|
export {
|
||||||
|
FeatureFlag,
|
||||||
|
FeatureFlags,
|
||||||
|
DefaultFeatureFlags,
|
||||||
|
isEnabled,
|
||||||
|
enable,
|
||||||
|
disable,
|
||||||
|
} from './auth/feature-flags';
|
||||||
export * from './common/errors';
|
export * from './common/errors';
|
||||||
|
|
||||||
// Artifacts
|
// Artifacts
|
||||||
|
|||||||
+25
-1
@@ -1,4 +1,5 @@
|
|||||||
import { Routes, Route, Link } from 'react-router-dom';
|
import { Routes, Route, Link } from 'react-router-dom';
|
||||||
|
import { useState } from 'react';
|
||||||
import DatasetExplorer from './pages/DatasetExplorer';
|
import DatasetExplorer from './pages/DatasetExplorer';
|
||||||
import ArtifactViewer from './pages/ArtifactViewer';
|
import ArtifactViewer from './pages/ArtifactViewer';
|
||||||
import MissionUpload from './pages/MissionUpload';
|
import MissionUpload from './pages/MissionUpload';
|
||||||
@@ -8,10 +9,26 @@ import PilotChecklist from './pages/PilotChecklist';
|
|||||||
import ReadinessDashboard from './pages/ReadinessDashboard';
|
import ReadinessDashboard from './pages/ReadinessDashboard';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
|
const [developerMode, setDeveloperMode] = useState(false);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div style={{ fontFamily: 'system-ui, sans-serif', maxWidth: 1200, margin: '0 auto', padding: 20 }}>
|
<div style={{ fontFamily: 'system-ui, sans-serif', maxWidth: 1200, margin: '0 auto', padding: 20 }}>
|
||||||
<header style={{ borderBottom: '2px solid #333', paddingBottom: 20, marginBottom: 20 }}>
|
<header style={{ borderBottom: '2px solid #333', paddingBottom: 20, marginBottom: 20 }}>
|
||||||
<h1>LandveX Intelligence Lab</h1>
|
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
|
||||||
|
<h1>LandveX {developerMode ? '(Developer Mode)' : ''}</h1>
|
||||||
|
<button
|
||||||
|
onClick={() => setDeveloperMode(!developerMode)}
|
||||||
|
style={{
|
||||||
|
padding: '8px 16px',
|
||||||
|
background: developerMode ? '#ffd700' : '#f0f0f0',
|
||||||
|
border: '1px solid #ccc',
|
||||||
|
borderRadius: 4,
|
||||||
|
cursor: 'pointer',
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{developerMode ? '🔓 Developer' : '🔒 Normal'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
<nav style={{ display: 'flex', gap: 20, marginTop: 10 }}>
|
<nav style={{ display: 'flex', gap: 20, marginTop: 10 }}>
|
||||||
<Link to="/">Dataset Explorer</Link>
|
<Link to="/">Dataset Explorer</Link>
|
||||||
<Link to="/upload">Mission Upload</Link>
|
<Link to="/upload">Mission Upload</Link>
|
||||||
@@ -19,6 +36,13 @@ function App() {
|
|||||||
<Link to="/health">Health</Link>
|
<Link to="/health">Health</Link>
|
||||||
<Link to="/pilot">Pilot 001</Link>
|
<Link to="/pilot">Pilot 001</Link>
|
||||||
<Link to="/readiness">Readiness</Link>
|
<Link to="/readiness">Readiness</Link>
|
||||||
|
{developerMode && (
|
||||||
|
<>
|
||||||
|
<Link to="/datasets">Datasets</Link>
|
||||||
|
<Link to="/models">Models</Link>
|
||||||
|
<Link to="/training">Training</Link>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
</header>
|
</header>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user