fix(awooop): 移除 tenants 公開內部狀態碼
All checks were successful
Code Review / ai-code-review (push) Successful in 14s
CD Pipeline / tests (push) Successful in 1m33s
CD Pipeline / build-and-deploy (push) Successful in 4m1s
CD Pipeline / post-deploy-checks (push) Successful in 1m35s

This commit is contained in:
Your Name
2026-06-15 03:19:09 +08:00
parent 6a2ceb7fa6
commit 8eff94a4f5
6 changed files with 115 additions and 35 deletions

View File

@@ -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,

View File

@@ -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"])

View File

@@ -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": "中風險",

View File

@@ -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": "中風險",

View File

@@ -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]">

View File

@@ -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)