fad476f115
Skills move from a global Git-only registry to a per-company library that orgs author and
version in-app — Git stays as the shared *starter* library.
Domain & persistence:
- Skill gains OrganizationId (null = shared builtin, visible to every org), Origin
(Builtin | Authored | Installed), AuthoredByMemberId. Identity is now
(OrganizationId, SkillKey, Version); the unique index uses NULLS NOT DISTINCT so builtins
stay unique by key+version while each org gets its own namespace (and can fork a builtin).
AddSkillOwnership migration backfills existing rows as Builtin.
- Owned GoldenExample rows are cloned in Skill.Index so a fork can't re-parent the source's
tracked entities.
Authoring (tenant, dynamic):
- POST /api/skills/authored — structured fields → same indexer pipeline (embedding +
publish gate apply identically), tagged org + author. POST /api/skills/{key}/fork copies a
builtin/global skill into your org as an editable Authored draft. List/Get are org-scoped
(your org + shared builtins). New Capability.ManageSkills (Owner + TeamOwner), audited.
- GET /api/skills/marketplace: read-only seam listing public skills across orgs (install is
the next step).
Security (from adversarial review — two confirmed criticals):
- Managing shared builtins is an operator action, not a tenant one. /index (posts arbitrary
content as a global builtin) and /sync (re-indexes the shared library) now require a
platform admin key (X-Skills-Admin-Key, fixed-time compare, fail-closed when unset) via
SkillAdminOptions — previously any authenticated user of any org could inject/poison global
skills. New test asserts an authenticated Owner without the key gets 403 on both.
UI: new /skills library page — browse shared + org skills grouped by key with their versions,
create / new-version / fork, golden-test editor + body, Draft/Published badge and the
publish-gate hint (needs roles + ≥1 golden test).
Verified: ArchitectureTests 8/8, IntegrationTests 46/46 (new SkillLibraryTests: org
isolation, version coexistence, fork, publish gate, Member 403, admin-gate 403), client build
green.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
39 lines
2.0 KiB
TypeScript
39 lines
2.0 KiB
TypeScript
import { Navigate, Route, Routes } from 'react-router'
|
|
import { Toaster } from '@/components/ui/sonner'
|
|
import { AnalyticsPage } from '@/pages/AnalyticsPage'
|
|
import { BoardPage } from '@/pages/BoardPage'
|
|
import { CartablePage } from '@/pages/CartablePage'
|
|
import { LoginPage } from '@/pages/LoginPage'
|
|
import { MembersPage } from '@/pages/MembersPage'
|
|
import { OrgChartPage } from '@/pages/OrgChartPage'
|
|
import { PerformancePage } from '@/pages/PerformancePage'
|
|
import { ReviewsPage } from '@/pages/ReviewsPage'
|
|
import { SeatsPage } from '@/pages/SeatsPage'
|
|
import { SkillsPage } from '@/pages/SkillsPage'
|
|
import { StructurePage } from '@/pages/StructurePage'
|
|
import { useAuth } from '@/store/auth'
|
|
|
|
export default function App() {
|
|
const token = useAuth((state) => state.token)
|
|
|
|
return (
|
|
<>
|
|
<Routes>
|
|
<Route path="/login" element={token ? <Navigate to="/" replace /> : <LoginPage />} />
|
|
<Route path="/" element={token ? <BoardPage /> : <Navigate to="/login" replace />} />
|
|
<Route path="/seats" element={token ? <SeatsPage /> : <Navigate to="/login" replace />} />
|
|
<Route path="/reviews" element={token ? <ReviewsPage /> : <Navigate to="/login" replace />} />
|
|
<Route path="/analytics" element={token ? <AnalyticsPage /> : <Navigate to="/login" replace />} />
|
|
<Route path="/cartable" element={token ? <CartablePage /> : <Navigate to="/login" replace />} />
|
|
<Route path="/members" element={token ? <MembersPage /> : <Navigate to="/login" replace />} />
|
|
<Route path="/org" element={token ? <OrgChartPage /> : <Navigate to="/login" replace />} />
|
|
<Route path="/structure" element={token ? <StructurePage /> : <Navigate to="/login" replace />} />
|
|
<Route path="/skills" element={token ? <SkillsPage /> : <Navigate to="/login" replace />} />
|
|
<Route path="/performance" element={token ? <PerformancePage /> : <Navigate to="/login" replace />} />
|
|
<Route path="*" element={<Navigate to="/" replace />} />
|
|
</Routes>
|
|
<Toaster richColors position="top-right" />
|
|
</>
|
|
)
|
|
}
|