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';
|
||||
|
||||
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';
|
||||
|
||||
// Artifacts
|
||||
|
||||
+25
-1
@@ -1,4 +1,5 @@
|
||||
import { Routes, Route, Link } from 'react-router-dom';
|
||||
import { useState } from 'react';
|
||||
import DatasetExplorer from './pages/DatasetExplorer';
|
||||
import ArtifactViewer from './pages/ArtifactViewer';
|
||||
import MissionUpload from './pages/MissionUpload';
|
||||
@@ -8,10 +9,26 @@ import PilotChecklist from './pages/PilotChecklist';
|
||||
import ReadinessDashboard from './pages/ReadinessDashboard';
|
||||
|
||||
function App() {
|
||||
const [developerMode, setDeveloperMode] = useState(false);
|
||||
|
||||
return (
|
||||
<div style={{ fontFamily: 'system-ui, sans-serif', maxWidth: 1200, margin: '0 auto', padding: 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 }}>
|
||||
<Link to="/">Dataset Explorer</Link>
|
||||
<Link to="/upload">Mission Upload</Link>
|
||||
@@ -19,6 +36,13 @@ function App() {
|
||||
<Link to="/health">Health</Link>
|
||||
<Link to="/pilot">Pilot 001</Link>
|
||||
<Link to="/readiness">Readiness</Link>
|
||||
{developerMode && (
|
||||
<>
|
||||
<Link to="/datasets">Datasets</Link>
|
||||
<Link to="/models">Models</Link>
|
||||
<Link to="/training">Training</Link>
|
||||
</>
|
||||
)}
|
||||
</nav>
|
||||
</header>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user