fix(awooop): 移除 tenants 公開內部狀態碼
This commit is contained in:
@@ -726,6 +726,26 @@ def _source_scope_id(index: int) -> str:
|
||||
return f"SRC-{index:03d}"
|
||||
|
||||
|
||||
def _public_source_risk(value: Any) -> str:
|
||||
risk = str(value or "").strip().lower()
|
||||
if risk in {"high", "medium", "low"}:
|
||||
return risk
|
||||
return "unknown"
|
||||
|
||||
|
||||
def _public_source_readiness(value: Any) -> str:
|
||||
readiness = str(value or "").strip().lower()
|
||||
if "refs" in readiness and "parity" in readiness:
|
||||
return "need_refs_evidence"
|
||||
if "target" in readiness and "decision" in readiness:
|
||||
return "need_target_decision"
|
||||
if "internal_remote" in readiness or ("remote" in readiness and "decision" in readiness):
|
||||
return "need_internal_remote_decision"
|
||||
if "scope" in readiness and "review" in readiness:
|
||||
return "need_scope_review"
|
||||
return "need_owner_evidence"
|
||||
|
||||
|
||||
def _public_surface_source_refs(surface: Mapping[str, Any]) -> list[str]:
|
||||
return [f"SRCREF-{index:03d}" for index, _ in enumerate(surface.get("source_keys") or [], start=1)]
|
||||
|
||||
@@ -778,8 +798,8 @@ def _build_source_repo_assets(
|
||||
"product_name": product["product_name"],
|
||||
"category": product["category"],
|
||||
"scope_status": row.get("scope_status") or "unknown",
|
||||
"readiness_state": row.get("readiness_state") or "unknown",
|
||||
"risk": row.get("risk") or "UNKNOWN",
|
||||
"readiness_state": _public_source_readiness(row.get("readiness_state")),
|
||||
"risk": _public_source_risk(row.get("risk")),
|
||||
"primary_ready": bool(row.get("primary_ready")),
|
||||
"blocker_count": len(row.get("blockers") or []),
|
||||
"runtime_gate_count": 0,
|
||||
|
||||
@@ -90,7 +90,21 @@ def test_tenant_asset_inventory_merges_products_routes_and_repos() -> None:
|
||||
inventory_payload = json.dumps(inventory, ensure_ascii=False)
|
||||
assert "owenhytsai" not in inventory_payload
|
||||
assert "nexu-io" not in inventory_payload
|
||||
assert "blocked_waiting_" not in inventory_payload
|
||||
assert "observe_scope_review" not in inventory_payload
|
||||
assert all(marker not in inventory_payload for marker in FORBIDDEN_PUBLIC_MARKERS)
|
||||
assert {item["risk"] for item in inventory["source_repos"]}.issubset(
|
||||
{"high", "medium", "low", "unknown"}
|
||||
)
|
||||
assert {item["readiness_state"] for item in inventory["source_repos"]}.issubset(
|
||||
{
|
||||
"need_refs_evidence",
|
||||
"need_target_decision",
|
||||
"need_internal_remote_decision",
|
||||
"need_scope_review",
|
||||
"need_owner_evidence",
|
||||
}
|
||||
)
|
||||
assert all(item["runtime_gate_count"] == 0 for item in inventory["source_repos"])
|
||||
assert all(item["action_button_count"] == 0 for item in inventory["public_routes"])
|
||||
assert all(item["runtime_gate_count"] == 0 for item in inventory["products"])
|
||||
|
||||
@@ -8595,7 +8595,7 @@
|
||||
"globalAssets": {
|
||||
"eyebrow": "全域納管",
|
||||
"title": "全域產品資產台帳",
|
||||
"subtitle": "把租戶資料表、正式網站入口、前後台產品、平台工具、設計系統、AI 工具與原始碼管控候選合併到同一個只讀視圖;前台只顯示脫敏範圍代號,不揭露原始 owner / namespace,且這不是建立專案庫、改路由、部署、掃描或執行期授權。",
|
||||
"subtitle": "把租戶資料表、正式網站入口、前後台產品、平台工具、設計系統、AI 工具與原始碼管控候選合併到同一個只讀視圖;前台只顯示脫敏範圍代號,不揭露原始負責人或命名空間,且這不是建立專案庫、改路由、部署、掃描或執行期授權。",
|
||||
"loading": "讀取資產盤",
|
||||
"itemsUnit": "項",
|
||||
"productsTitle": "產品 / 專案納管",
|
||||
@@ -8663,6 +8663,17 @@
|
||||
"ownerResponseRequired": "待負責人回覆",
|
||||
"unknown": "待分類"
|
||||
},
|
||||
"boundaryItems": {
|
||||
"readOnly": "目前只建立只讀資產索引,不修改租戶、路由、主機或專案庫。",
|
||||
"redactedIdentity": "前台只顯示範圍代號,不揭露原始負責人、命名空間或完整專案庫名稱。",
|
||||
"noRawRepository": "原始碼來源只保留脫敏對照與證據參照,正式頁不顯示完整來源字串。",
|
||||
"ownerResponseZero": "負責人回覆仍未接受,不能把候選範圍視為已核准。",
|
||||
"runtimeGateZero": "執行期閘門仍為 0,不啟動掃描、修復、部署或主機操作。",
|
||||
"noActionButtons": "此頁不提供任何可執行按鈕。",
|
||||
"noRepoCreation": "未取得正式決策前,不建立專案庫、不改可見性、不同步分支或標籤。",
|
||||
"noRouteChange": "未取得路由負責人證據前,不改 Nginx、公開入口或上游設定。",
|
||||
"noDeployment": "此頁只顯示治理狀態,不代表部署、主要來源切換或正式資安接受。"
|
||||
},
|
||||
"sourceRisk": {
|
||||
"high": "高風險",
|
||||
"medium": "中風險",
|
||||
|
||||
@@ -8595,7 +8595,7 @@
|
||||
"globalAssets": {
|
||||
"eyebrow": "全域納管",
|
||||
"title": "全域產品資產台帳",
|
||||
"subtitle": "把租戶資料表、正式網站入口、前後台產品、平台工具、設計系統、AI 工具與原始碼管控候選合併到同一個只讀視圖;前台只顯示脫敏範圍代號,不揭露原始 owner / namespace,且這不是建立專案庫、改路由、部署、掃描或執行期授權。",
|
||||
"subtitle": "把租戶資料表、正式網站入口、前後台產品、平台工具、設計系統、AI 工具與原始碼管控候選合併到同一個只讀視圖;前台只顯示脫敏範圍代號,不揭露原始負責人或命名空間,且這不是建立專案庫、改路由、部署、掃描或執行期授權。",
|
||||
"loading": "讀取資產盤",
|
||||
"itemsUnit": "項",
|
||||
"productsTitle": "產品 / 專案納管",
|
||||
@@ -8663,6 +8663,17 @@
|
||||
"ownerResponseRequired": "待負責人回覆",
|
||||
"unknown": "待分類"
|
||||
},
|
||||
"boundaryItems": {
|
||||
"readOnly": "目前只建立只讀資產索引,不修改租戶、路由、主機或專案庫。",
|
||||
"redactedIdentity": "前台只顯示範圍代號,不揭露原始負責人、命名空間或完整專案庫名稱。",
|
||||
"noRawRepository": "原始碼來源只保留脫敏對照與證據參照,正式頁不顯示完整來源字串。",
|
||||
"ownerResponseZero": "負責人回覆仍未接受,不能把候選範圍視為已核准。",
|
||||
"runtimeGateZero": "執行期閘門仍為 0,不啟動掃描、修復、部署或主機操作。",
|
||||
"noActionButtons": "此頁不提供任何可執行按鈕。",
|
||||
"noRepoCreation": "未取得正式決策前,不建立專案庫、不改可見性、不同步分支或標籤。",
|
||||
"noRouteChange": "未取得路由負責人證據前,不改 Nginx、公開入口或上游設定。",
|
||||
"noDeployment": "此頁只顯示治理狀態,不代表部署、主要來源切換或正式資安接受。"
|
||||
},
|
||||
"sourceRisk": {
|
||||
"high": "高風險",
|
||||
"medium": "中風險",
|
||||
|
||||
@@ -243,6 +243,18 @@ const ownerResponseValidationTenantBoundaries = [
|
||||
"action_buttons_allowed=false",
|
||||
];
|
||||
|
||||
const globalAssetBoundaryItems = [
|
||||
"readOnly",
|
||||
"redactedIdentity",
|
||||
"noRawRepository",
|
||||
"ownerResponseZero",
|
||||
"runtimeGateZero",
|
||||
"noActionButtons",
|
||||
"noRepoCreation",
|
||||
"noRouteChange",
|
||||
"noDeployment",
|
||||
];
|
||||
|
||||
const ASSET_STATUS_TONES: Record<string, AssetTone> = {
|
||||
verified: "steady",
|
||||
read_only_visible: "steady",
|
||||
@@ -268,26 +280,6 @@ const ASSET_STATUS_KEYS: Record<string, string> = {
|
||||
owner_response_required: "ownerResponseRequired",
|
||||
};
|
||||
|
||||
const SOURCE_RISK_KEYS: Record<string, string> = {
|
||||
HIGH: "high",
|
||||
MEDIUM: "medium",
|
||||
LOW: "low",
|
||||
};
|
||||
|
||||
const SOURCE_READINESS_KEYS: Record<string, string> = {
|
||||
blocked_waiting_refs_parity: "refsParity",
|
||||
blocked_waiting_target_decision: "targetDecision",
|
||||
blocked_waiting_internal_remote_decision: "internalRemoteDecision",
|
||||
observe_scope_review: "scopeReview",
|
||||
};
|
||||
|
||||
const SOURCE_ACTION_KEYS: Record<string, string> = {
|
||||
blocked_waiting_refs_parity: "refsParity",
|
||||
blocked_waiting_target_decision: "targetDecision",
|
||||
blocked_waiting_internal_remote_decision: "internalRemoteDecision",
|
||||
observe_scope_review: "scopeReview",
|
||||
};
|
||||
|
||||
function assetToneClass(tone: AssetTone) {
|
||||
if (tone === "steady") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]";
|
||||
if (tone === "locked") return "border-[#b7add8] bg-[#f8f6fc] text-[#5b4b91]";
|
||||
@@ -315,25 +307,52 @@ function assetCategoryLabel(category: string, t: ReturnType<typeof useTranslatio
|
||||
return t(`categories.${key}` as never);
|
||||
}
|
||||
|
||||
function sourceRiskKey(risk: string) {
|
||||
const normalized = risk.toLowerCase();
|
||||
if (normalized === "high" || normalized === "medium" || normalized === "low") return normalized;
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function sourceReadinessKey(readiness: string) {
|
||||
const normalized = readiness.toLowerCase();
|
||||
if (normalized === "need_refs_evidence" || (normalized.includes("refs") && normalized.includes("parity"))) {
|
||||
return "refsParity";
|
||||
}
|
||||
if (normalized === "need_target_decision" || (normalized.includes("target") && normalized.includes("decision"))) {
|
||||
return "targetDecision";
|
||||
}
|
||||
if (
|
||||
normalized === "need_internal_remote_decision" ||
|
||||
normalized.includes("internal_remote") ||
|
||||
(normalized.includes("remote") && normalized.includes("decision"))
|
||||
) {
|
||||
return "internalRemoteDecision";
|
||||
}
|
||||
if (normalized === "need_scope_review" || (normalized.includes("scope") && normalized.includes("review"))) {
|
||||
return "scopeReview";
|
||||
}
|
||||
return "unknown";
|
||||
}
|
||||
|
||||
function sourceRiskLabel(risk: string, t: ReturnType<typeof useTranslations>) {
|
||||
const key = SOURCE_RISK_KEYS[risk] ?? "unknown";
|
||||
return t(`sourceRisk.${key}` as never);
|
||||
return t(`sourceRisk.${sourceRiskKey(risk)}` as never);
|
||||
}
|
||||
|
||||
function sourceReadinessLabel(readiness: string, t: ReturnType<typeof useTranslations>) {
|
||||
const key = SOURCE_READINESS_KEYS[readiness] ?? "unknown";
|
||||
const key = sourceReadinessKey(readiness);
|
||||
return t(`sourceReadiness.${key}` as never);
|
||||
}
|
||||
|
||||
function sourceActionLabel(readiness: string, t: ReturnType<typeof useTranslations>) {
|
||||
const key = SOURCE_ACTION_KEYS[readiness] ?? "unknown";
|
||||
const key = sourceReadinessKey(readiness);
|
||||
return t(`sourceActions.${key}` as never);
|
||||
}
|
||||
|
||||
function sourceRiskClass(risk: string) {
|
||||
if (risk === "HIGH") return "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]";
|
||||
if (risk === "MEDIUM") return "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]";
|
||||
if (risk === "LOW") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]";
|
||||
const key = sourceRiskKey(risk);
|
||||
if (key === "high") return "border-[#e2a29b] bg-[#fff0ef] text-[#9f2f25]";
|
||||
if (key === "medium") return "border-[#d9b36f] bg-[#fff7e8] text-[#8a5a08]";
|
||||
if (key === "low") return "border-[#9bc7a4] bg-[#f0faf2] text-[#17602a]";
|
||||
return "border-[#d8d3c7] bg-[#faf9f3] text-[#5f5b52]";
|
||||
}
|
||||
|
||||
@@ -812,7 +831,6 @@ function GlobalAssetCoveragePanel({ inventory }: { inventory: TenantAssetInvento
|
||||
const products = inventory?.products ?? [];
|
||||
const publicRoutes = inventory?.public_routes ?? [];
|
||||
const sourceRepos = inventory?.source_repos ?? [];
|
||||
const boundaries = inventory?.boundaries ?? [];
|
||||
const metrics = [
|
||||
{
|
||||
key: "products",
|
||||
@@ -943,9 +961,9 @@ function GlobalAssetCoveragePanel({ inventory }: { inventory: TenantAssetInvento
|
||||
<div className="min-w-0 border border-[#e0ddd4] bg-[#faf9f3] px-4 py-3">
|
||||
<p className="text-xs font-semibold text-[#77736a]">{t("boundaryTitle")}</p>
|
||||
<p className="mt-2 text-sm font-semibold text-[#141413]">{t("boundaryLead")}</p>
|
||||
<div className="mt-4 grid gap-2 break-all font-mono text-xs text-[#141413]">
|
||||
{boundaries.slice(0, 9).map((boundary) => (
|
||||
<span key={boundary}>{boundary}</span>
|
||||
<div className="mt-4 grid gap-2 text-xs leading-5 text-[#141413]">
|
||||
{globalAssetBoundaryItems.map((item) => (
|
||||
<span key={item}>{t(`boundaryItems.${item}` as never)}</span>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-4 grid gap-2 text-xs leading-5 text-[#5f5b52]">
|
||||
|
||||
@@ -11683,6 +11683,9 @@ def validate(root: Path) -> None:
|
||||
"{repo.risk}",
|
||||
"{repo.readiness_state}",
|
||||
"namespace_redacted={String(repo.source_namespace_redacted)}",
|
||||
"repo_owner_namespace_redacted=true",
|
||||
"raw_repository_namespace_visible=false",
|
||||
"public_api_raw_repo_namespace_allowed=false",
|
||||
]:
|
||||
assert_text_not_contains("awooop_tenants_page.raw_source_control_status_render", awooop_tenants_page, text)
|
||||
assert_text_not_contains(
|
||||
@@ -11722,6 +11725,9 @@ def validate(root: Path) -> None:
|
||||
"待參照一致性證據",
|
||||
"待目標與可見性決策",
|
||||
"未接受前不得建立專案庫或改可見性",
|
||||
"不揭露原始負責人或命名空間",
|
||||
"不修改租戶、路由、主機或專案庫",
|
||||
"不顯示完整來源字串",
|
||||
]:
|
||||
assert_text_contains("web_messages.zh-TW.awooop.tenants.global_assets_redaction", tenant_global_assets_messages_zh, text)
|
||||
assert_text_contains("web_messages.en.awooop.tenants.global_assets_redaction", tenant_global_assets_messages_en, text)
|
||||
|
||||
Reference in New Issue
Block a user