feat: surface active governance overrides

This commit is contained in:
kris
2026-03-29 17:45:03 +08:00
parent f813b6e5c0
commit 8a133a4f78
4 changed files with 165 additions and 16 deletions

View File

@@ -683,6 +683,14 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
def _resolve_project(account: dict[str, Any], project_id: str | None) -> dict[str, Any]:
return legacy.resolve_target_project(account["id"], project_id or None, username=account["username"])
def _resolve_project_for_read(account: dict[str, Any], project_id: str | None) -> dict[str, Any] | None:
if project_id:
return legacy.resolve_target_project(account["id"], project_id, username=account["username"])
return legacy.db.fetch_one(
"SELECT * FROM projects WHERE user_id = ? ORDER BY created_at ASC LIMIT 1",
(account["id"],),
)
def _resolve_assistant(account: dict[str, Any], assistant_id: str | None, project_id: str = "") -> dict[str, Any] | None:
return legacy.resolve_target_assistant(account["id"], assistant_id or None, project_id)
@@ -4522,10 +4530,10 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
platform: str | None = Query(default=None),
account: dict[str, Any] = Depends(legacy.require_approved),
) -> dict[str, Any]:
project = _resolve_project(account, project_id or None)
project = _resolve_project_for_read(account, project_id or None)
return _effective_policy_payload(
subject_account=account,
subject_project_id=project["id"],
subject_project_id=(project or {}).get("id", ""),
platform=platform or "",
)
@@ -4534,22 +4542,23 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
project_id: str | None = Query(default=None),
account: dict[str, Any] = Depends(legacy.require_approved),
) -> dict[str, Any]:
project = _resolve_project(account, project_id or None)
project = _resolve_project_for_read(account, project_id or None)
resolved_project_id = (project or {}).get("id", "")
scope_row = _policy_scope_row(
scope_kind="user_global",
subject_user_id=account["id"],
subject_project_id=project["id"],
subject_project_id=resolved_project_id,
)
payload = _bundle_with_versions(
scope_row,
fallback_kind="user_global",
fallback_user_id=account["id"],
fallback_project_id=project["id"],
fallback_project_id=resolved_project_id,
active_version_only=True,
)
payload["effective_policy"] = _effective_policy_payload(
subject_account=account,
subject_project_id=project["id"],
subject_project_id=resolved_project_id,
platform="",
)["effective_policy"]
return payload
@@ -4600,11 +4609,11 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
project_id: str | None = Query(default=None),
account: dict[str, Any] = Depends(legacy.require_approved),
) -> dict[str, Any]:
project = _resolve_project(account, project_id or None)
project = _resolve_project_for_read(account, project_id or None)
scope_row = _policy_scope_row(
scope_kind="user_global",
subject_user_id=account["id"],
subject_project_id=project["id"],
subject_project_id=(project or {}).get("id", ""),
)
items = _list_policy_versions(scope_row)
return {"items": items, "count": len(items)}
@@ -4616,13 +4625,14 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
limit: int = Query(default=20, ge=1, le=100),
account: dict[str, Any] = Depends(legacy.require_approved),
) -> dict[str, Any]:
project = _resolve_project(account, project_id or None)
project = _resolve_project_for_read(account, project_id or None)
resolved_project_id = (project or {}).get("id", "")
normalized_platform = _normalize_policy_platform(platform)
where_clauses = [
"WHERE scope.subject_user_id = ?",
"AND (scope.subject_project_id = ? OR scope.subject_project_id = '')",
]
params: list[Any] = [account["id"], project["id"]]
params: list[Any] = [account["id"], resolved_project_id]
if normalized_platform:
where_clauses.append("AND (scope.platform = '' OR scope.platform = ?)")
params.append(normalized_platform)
@@ -4670,12 +4680,13 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
project_id: str | None = Query(default=None),
account: dict[str, Any] = Depends(legacy.require_approved),
) -> dict[str, Any]:
project = _resolve_project(account, project_id or None)
project = _resolve_project_for_read(account, project_id or None)
resolved_project_id = (project or {}).get("id", "")
normalized_platform = _normalize_policy_platform(platform)
scope_row = _policy_scope_row(
scope_kind="user_platform",
subject_user_id=account["id"],
subject_project_id=project["id"],
subject_project_id=resolved_project_id,
platform=normalized_platform,
)
payload = _bundle_with_versions(
@@ -4683,12 +4694,12 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
fallback_kind="user_platform",
fallback_platform=normalized_platform,
fallback_user_id=account["id"],
fallback_project_id=project["id"],
fallback_project_id=resolved_project_id,
active_version_only=True,
)
payload["effective_policy"] = _effective_policy_payload(
subject_account=account,
subject_project_id=project["id"],
subject_project_id=resolved_project_id,
platform=normalized_platform,
)["effective_policy"]
return payload
@@ -4743,12 +4754,12 @@ def register_oneliner_routes(app: Any, legacy: Any) -> None:
project_id: str | None = Query(default=None),
account: dict[str, Any] = Depends(legacy.require_approved),
) -> dict[str, Any]:
project = _resolve_project(account, project_id or None)
project = _resolve_project_for_read(account, project_id or None)
normalized_platform = _normalize_policy_platform(platform)
scope_row = _policy_scope_row(
scope_kind="user_platform",
subject_user_id=account["id"],
subject_project_id=project["id"],
subject_project_id=(project or {}).get("id", ""),
platform=normalized_platform,
)
items = _list_policy_versions(scope_row)

View File

@@ -130,6 +130,56 @@ class MainAgentGovernanceTests(unittest.TestCase):
"member_headers": {"Authorization": f"Bearer {member_token}"},
}
def _seed_approved_member_without_project(self) -> dict[str, Any]:
now = self.db_module.utc_now()
admin_id = "acct_admin"
member_id = "acct_member_noproject"
model_id = "model_default"
admin_token = "token_admin"
member_token = "token_member_noproject"
self.core.db.execute(
"""
INSERT INTO accounts (
id, username, password_hash, password_salt, display_name, role, approval_status,
approved_by, approved_at, preferred_analysis_model_id, created_at, updated_at
) VALUES (?, ?, 'hash', 'salt', ?, ?, 'approved', ?, ?, ?, ?, ?)
""",
(admin_id, "admin", "Admin", "super_admin", admin_id, now, model_id, now, now),
)
self.core.db.execute(
"""
INSERT INTO accounts (
id, username, password_hash, password_salt, display_name, role, approval_status,
approved_by, approved_at, preferred_analysis_model_id, created_at, updated_at
) VALUES (?, ?, 'hash', 'salt', ?, ?, 'approved', ?, ?, ?, ?, ?)
""",
(member_id, "member_noproject", "Member No Project", "operator", admin_id, now, model_id, now, now),
)
self.core.db.execute(
"""
INSERT INTO model_profiles (
id, owner_account_id, name, provider, base_url, api_key, model_name,
is_system, is_default, created_at, updated_at
) VALUES (?, NULL, 'Default Model', 'openai_compat', 'http://127.0.0.1:8317/v1', '', 'GLM-5', 1, 1, ?, ?)
""",
(model_id, now, now),
)
self.core.db.execute(
"INSERT INTO auth_tokens (token, account_id, created_at) VALUES (?, ?, ?)",
(admin_token, admin_id, now),
)
self.core.db.execute(
"INSERT INTO auth_tokens (token, account_id, created_at) VALUES (?, ?, ?)",
(member_token, member_id, now),
)
return {
"admin_id": admin_id,
"member_id": member_id,
"admin_headers": {"Authorization": f"Bearer {admin_token}"},
"member_headers": {"Authorization": f"Bearer {member_token}"},
}
def test_effective_policy_merges_system_user_global_and_platform_layers(self) -> None:
system_response = self.client.put(
"/v2/admin/oneliner/governance/system/main-agent",
@@ -378,6 +428,33 @@ class MainAgentGovernanceTests(unittest.TestCase):
self.assertEqual(system_read.status_code, 200, system_read.text)
self.assertEqual(system_read.json()["current_version"]["title"], "System baseline")
def test_governance_read_endpoints_do_not_create_default_project_when_project_is_missing(self) -> None:
self._clear_tables()
ctx = self._seed_approved_member_without_project()
before_count = self.core.db.fetch_one("SELECT COUNT(*) AS count FROM projects WHERE user_id = ?", (ctx["member_id"],))
self.assertEqual(int((before_count or {}).get("count") or 0), 0)
effective_response = self.client.get(
"/v2/oneliner/governance/effective",
headers=ctx["member_headers"],
params={"platform": "douyin"},
)
self.assertEqual(effective_response.status_code, 200, effective_response.text)
effective_payload = effective_response.json()
self.assertEqual(effective_payload["project_id"], "")
self.assertEqual(effective_payload["layers"], [])
global_response = self.client.get(
"/v2/oneliner/governance/user/global",
headers=ctx["member_headers"],
)
self.assertEqual(global_response.status_code, 200, global_response.text)
self.assertEqual(global_response.json()["scope"]["subject_project_id"], "")
self.assertIsNone(global_response.json()["current_version"])
after_count = self.core.db.fetch_one("SELECT COUNT(*) AS count FROM projects WHERE user_id = ?", (ctx["member_id"],))
self.assertEqual(int((after_count or {}).get("count") or 0), 0)
def test_admin_governance_directory_lists_accounts_and_projects(self) -> None:
response = self.client.get(
"/v2/admin/oneliner/governance/directory",

View File

@@ -965,6 +965,7 @@ function renderOneLinerMessagesHtml() {
const result = message.result || {};
const plan = message.plan || {};
const executionCard = result.execution_card || {};
const activeAdminOverrideNotice = executionCard.active_admin_override_notice || null;
const actions = safeArray(plan.suggested_actions);
const secondaryActions = safeArray(executionCard.secondary_actions);
return `
@@ -995,6 +996,16 @@ function renderOneLinerMessagesHtml() {
${executionCard.readiness_label ? `<span class="tag ${executionCard.readiness_score >= 75 ? "green" : executionCard.readiness_score >= 50 ? "blue" : "orange"}">${escapeHtml(executionCard.readiness_label)} ${escapeHtml(formatNumber(executionCard.readiness_score || 0))}</span>` : ""}
${executionCard.primary_action?.key ? `<span class="tag clickable-tag" data-action="${escapeHtml(executionCard.primary_action.key)}">${escapeHtml(executionCard.primary_action.label || "执行下一步")}</span>` : ""}
</div>
${activeAdminOverrideNotice?.title ? `
<div class="task-item compact" style="margin-top:10px; border-color:rgba(245, 158, 11, 0.28); background:linear-gradient(180deg, rgba(255, 250, 240, 0.98) 0%, rgba(255, 255, 255, 0.98) 100%);">
<h4>管理员覆盖生效中</h4>
<p>${escapeHtml(activeAdminOverrideNotice.summary || "当前这轮执行会优先遵循管理员覆盖,再叠加你的个人策略。")}</p>
<div class="task-meta">
<span class="tag orange">${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")}</span>
${activeAdminOverrideNotice.platform_label ? `<span class="tag">${escapeHtml(activeAdminOverrideNotice.platform_label)}</span>` : ""}
</div>
</div>
` : ""}
${safeArray(executionCard.evidence).length ? `
<div class="list" style="margin-top:10px;">
${safeArray(executionCard.evidence).slice(0, 2).map((item) => `
@@ -1053,6 +1064,7 @@ function renderOneLinerUi() {
const input = document.querySelector('[data-role="oneliner-input"]');
const profile = appState.onelinerProfile;
const effective = appState.onelinerGovernanceEffective;
const activeAdminOverrideNotice = effective?.active_admin_override_notice || null;
const highlights = summarizePolicyHighlights(effective?.effective_policy || {}, effective?.platform || "");
const layers = safeArray(effective?.layers);
if (fab) {
@@ -1072,6 +1084,16 @@ function renderOneLinerUi() {
${highlights.map((item) => `<span class="tag green">${escapeHtml(item)}</span>`).join("")}
<span class="tag clickable-tag" data-action="open-user-global-policy">我的策略</span>
</div>
${activeAdminOverrideNotice?.title ? `
<div class="task-item compact" style="margin-top:10px; border-color:rgba(245, 158, 11, 0.28); background:linear-gradient(180deg, rgba(255, 250, 240, 0.98) 0%, rgba(255, 255, 255, 0.98) 100%);">
<h4>管理员覆盖生效中</h4>
<p>${escapeHtml(activeAdminOverrideNotice.summary || "当前主 Agent 会优先遵循管理员覆盖层。")}</p>
<div class="task-meta">
<span class="tag orange">${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")}</span>
<span class="tag clickable-tag" data-action="open-user-global-policy">查看我的策略</span>
</div>
</div>
` : ""}
`;
}
if (sessions) sessions.innerHTML = renderOneLinerSessionTabs();
@@ -3333,6 +3355,7 @@ function getAdminOverrideTargetSummary(target = appState.adminOverrideTarget) {
function renderGovernanceSummaryCard({ title, subtitle, effective, primaryAction = "", primaryLabel = "编辑策略", secondaryAction = "", secondaryLabel = "", secondaryPlatform = "", actions = null }) {
const layers = safeArray(effective?.layers);
const highlights = summarizePolicyHighlights(effective?.effective_policy || {}, effective?.platform || secondaryPlatform || "");
const activeAdminOverrideNotice = effective?.active_admin_override_notice || null;
const resolvedActions = safeArray(actions?.length ? actions : [
primaryAction ? { action: primaryAction, label: primaryLabel } : null,
secondaryAction ? { action: secondaryAction, label: secondaryLabel, platform: secondaryPlatform } : null
@@ -3345,6 +3368,16 @@ function renderGovernanceSummaryCard({ title, subtitle, effective, primaryAction
${layers.map((layer) => `<span class="tag ${layer.scope_kind === "admin_override" ? "orange" : "blue"}">${escapeHtml(policyScopeTagLabel(layer.scope_kind, layer.scope?.platform || effective?.platform || ""))}</span>`).join("") || `<span class="tag">尚未发布</span>`}
${highlights.map((item) => `<span class="tag green">${escapeHtml(item)}</span>`).join("")}
</div>
${activeAdminOverrideNotice?.title ? `
<div class="task-item compact" style="margin-top:10px; border-color:rgba(245, 158, 11, 0.28); background:linear-gradient(180deg, rgba(255, 250, 240, 0.98) 0%, rgba(255, 255, 255, 0.98) 100%);">
<h4>管理员覆盖生效中</h4>
<p>${escapeHtml(activeAdminOverrideNotice.summary || activeAdminOverrideNotice.title || "当前这层管理员覆盖会优先于你的个人策略生效。")}</p>
<div class="task-meta">
<span class="tag orange">${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")}</span>
${activeAdminOverrideNotice.platform_label ? `<span class="tag">${escapeHtml(activeAdminOverrideNotice.platform_label)}</span>` : ""}
</div>
</div>
` : ""}
${resolvedActions.length ? `
<div class="task-meta" style="margin-top:10px;">
${resolvedActions.map((item) => `
@@ -4900,6 +4933,7 @@ function renderPlaybookScreen() {
const currentModel = getCurrentModelProfile();
const currentAssistant = getSelectedAssistant();
const localCatalog = appState.localModelCatalog || {};
const activeAdminOverrideNotice = appState.onelinerGovernanceEffective?.active_admin_override_notice || null;
const gatewayModels = safeArray(localCatalog.models).map((item) => item.id).filter(Boolean);
const tabs = [
{ value: "workspace", label: "当前 Agent 工作台" },
@@ -4951,6 +4985,16 @@ function renderPlaybookScreen() {
<span class="tag clickable-tag" data-action="open-oneliner-profile">编辑配置</span>
</div>
</div>
${activeAdminOverrideNotice?.title ? `
<div class="task-item compact" style="margin-top:12px; border-color:rgba(245, 158, 11, 0.28); background:linear-gradient(180deg, rgba(255, 250, 240, 0.98) 0%, rgba(255, 255, 255, 0.98) 100%);">
<h4>管理员覆盖生效中</h4>
<p>${escapeHtml(activeAdminOverrideNotice.summary || "当前 OneLiner 和平台 Agent 都会先遵循管理员覆盖层。")}</p>
<div class="task-meta">
<span class="tag orange">${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")}</span>
<span class="tag clickable-tag" data-action="open-user-global-policy">看我的策略</span>
</div>
</div>
` : ""}
${renderGovernanceSummaryCard({
title: "我的策略与历史",
subtitle: appState.userGlobalPolicy?.current_version?.summary || "你和主 Agent 的策略对话,会先沉淀成用户全局策略,再按需要下放到单平台。",
@@ -5320,6 +5364,7 @@ function renderStrategyScreen() {
const activeTab = getActiveDetailTab("strategyDetailTab", tabs);
const project = getSelectedProject();
const platform = appState.onelinerGovernanceEffective?.platform || appState.onelinerProfile?.default_platform || getPreferredPlatform();
const activeAdminOverrideNotice = appState.onelinerGovernanceEffective?.active_admin_override_notice || null;
return screenShell(
"我的策略",
"把你和主 Agent 的对话沉淀成可查看、可回滚、可追溯的个人策略层。",
@@ -5328,6 +5373,16 @@ function renderStrategyScreen() {
<div class="hero-card">
<h3>当前策略工作区</h3>
<p>${escapeHtml(project?.name || "当前项目")} · ${escapeHtml(platformLabel(platform))}。这里展示系统默认、你的个性化策略和管理员覆盖是如何叠加生效的。</p>
${activeAdminOverrideNotice?.title ? `
<div class="task-item compact" style="margin-top:14px; border-color:rgba(245, 158, 11, 0.28); background:linear-gradient(180deg, rgba(255, 250, 240, 0.98) 0%, rgba(255, 255, 255, 0.98) 100%);">
<h4>管理员覆盖生效中</h4>
<p>${escapeHtml(activeAdminOverrideNotice.summary || "当前项目下的部分策略被管理员覆盖层托底。")}</p>
<div class="task-meta">
<span class="tag orange">${escapeHtml(activeAdminOverrideNotice.title || "管理员覆盖")}</span>
${activeAdminOverrideNotice.platform_label ? `<span class="tag">${escapeHtml(activeAdminOverrideNotice.platform_label)}</span>` : ""}
</div>
</div>
` : ""}
</div>
<div class="panel pad" style="margin-top:18px;">
<div class="panel-head">

View File

@@ -47,6 +47,7 @@ test("agent screen excludes quota and registry panels and uses page tabs", () =>
assert.match(source, /renderGovernanceSummaryCard\(/);
assert.match(source, /open-user-global-policy/);
assert.match(source, /open-user-platform-policy/);
assert.match(source, /active_admin_override_notice/);
});
test("discovery, production, and admin screens use page tabs for heavy content", () => {
@@ -125,9 +126,11 @@ test("agent control surfaces load governance endpoints for user and admin summar
test("oneliner meta and action handlers expose governance entry points", () => {
const meta = extractBetween(APP, "function renderOneLinerUi()", "function openOneLinerPanel()");
const messages = extractBetween(APP, "function renderOneLinerMessagesHtml()", "function renderOneLinerUi()");
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
assert.match(meta, /open-user-global-policy/);
assert.match(meta, /policyScopeTagLabel/);
assert.match(messages, /active_admin_override_notice/);
assert.match(actions, /name === "open-user-global-policy"/);
assert.match(actions, /name === "open-system-main-policy"/);
});
@@ -181,10 +184,13 @@ test("governance UI exposes admin override target picker and history rollback en
test("user governance UI exposes personal history and rollback entrypoints", () => {
const playbook = extractBetween(APP, "function renderPlaybookScreen()", "function renderProductionScreen()");
const strategy = extractBetween(APP, "function renderStrategyScreen()", "function renderCreditsScreen()");
const actions = extractBetween(APP, "document.addEventListener(\"click\", async (event) => {", "document.addEventListener(\"submit\", async (event) => {");
assert.match(playbook, /open-user-global-policy-history/);
assert.match(playbook, /open-user-platform-policy-history/);
assert.match(strategy, /active_admin_override_notice/);
assert.match(strategy, /管理员覆盖生效中/);
assert.match(actions, /name === "open-user-global-policy-history"/);
assert.match(actions, /name === "open-user-platform-policy-history"/);