Compare commits

...
Author SHA1 Message Date
weichen 10635387e0 Gate project override settings to owners and admins
Why: S9 acceptance finding 3 — the project settings modal exposed the
host override settings (including the model/key group, masked) to plain
project members; the duties data plane was already permission-enforced,
but the host settings surface had no gate.

Evidence: vitest 34 files / 293 tests pass with the change; tsc reports
no errors in the modified file. Ruling per the acceptance plan P1-B:
the modal entry stays (granted duty managers need the RDM tabs), while
the host override settings group renders only for the project owner or
a system admin; RDM tabs keep their own fail-closed gating.

Impact: ProjectSettingsModal project tab renders an explanatory empty
state for non-privileged viewers; members/duties/matrix tabs unchanged.
Refs: S9 acceptance report P1-B
2026-10-16 05:09:58 +00:00
weichen a81e94a4f2 Merge pull request 'Show the layered Python environment in admin'
Reviewed against the layered environment plan. Required CI on the current head passed where this repository runs CI.
2026-10-15 14:24:46 +00:00
+7
View File
@@ -19,6 +19,11 @@ export function ProjectSettingsModal() {
const rdmDocReviewAvailable = useStore(s => s.rdmDocReviewAvailable) const rdmDocReviewAvailable = useStore(s => s.rdmDocReviewAvailable)
const rdmPermMatrixAvailable = useStore(s => s.rdmPermMatrixAvailable) const rdmPermMatrixAvailable = useStore(s => s.rdmPermMatrixAvailable)
const owner = useStore(s => s.me?.owner) const owner = useStore(s => s.me?.owner)
const isAdmin = useStore(s => s.me?.isAdmin ?? false)
// P1-B 裁定:宿主覆盖设置(含模型与密钥)仅项目负责人/系统管理员可查看与修改;
// RDM 页签(成员/文档职责/项目权限)不在此列——数据面由 RDM 权限自行收敛,
// 授权的职责管理者仍可进入。
const projectPrivileged = !!project && (project.owner === owner || isAdmin)
const [tab, setTab] = useState('project') const [tab, setTab] = useState('project')
const [schema, setSchema] = useState<SettingDesc[]>([]) const [schema, setSchema] = useState<SettingDesc[]>([])
const [draft, setDraft] = useState<Record<string, any>>({}) const [draft, setDraft] = useState<Record<string, any>>({})
@@ -85,6 +90,8 @@ export function ProjectSettingsModal() {
</>} </>}
{tab !== 'members' && tab !== 'duties' && tab !== 'matrix' && !loaded ? ( {tab !== 'members' && tab !== 'duties' && tab !== 'matrix' && !loaded ? (
<EmptyState icon="settings">{error || '加载中…'}</EmptyState> <EmptyState icon="settings">{error || '加载中…'}</EmptyState>
) : tab !== 'members' && tab !== 'duties' && tab !== 'matrix' && !projectPrivileged ? (
<EmptyState icon="settings">项目覆盖设置(含模型与密钥)仅项目负责人或系统管理员可查看与修改。</EmptyState>
) : tab !== 'members' && tab !== 'duties' && tab !== 'matrix' ? ( ) : tab !== 'members' && tab !== 'duties' && tab !== 'matrix' ? (
<> <>
<div className="empty-note" style={{ marginBottom: 10 }}> <div className="empty-note" style={{ marginBottom: 10 }}>