diff --git a/backend/app/data/source_versions.py b/backend/app/data/source_versions.py
index 4e389fb0..2e0dedfe 100644
--- a/backend/app/data/source_versions.py
+++ b/backend/app/data/source_versions.py
@@ -110,6 +110,16 @@ def load_manifest(artifact_id: str | None) -> dict[str, Any] | None:
return doc if isinstance(doc, dict) else None
+def _signer_verified(artifact_id: str | None) -> bool:
+ """导入清单里记下的「署名是否认证」;没有清单(简单导入)、读不出来的照实算未认证。"""
+ try:
+ doc = load_manifest(artifact_id)
+ except ValueError:
+ return False
+ signer = (doc or {}).get("signed_by")
+ return bool(signer.get("verified")) if isinstance(signer, dict) else False
+
+
def _rows_of(report: dict[str, Any] | None) -> dict[str, int]:
"""构建回执里各表的行数(执行器回执、期 1 的解析回执都是 tables: [{name, rows}])。"""
out: dict[str, int] = {}
@@ -1055,6 +1065,8 @@ async def import_records(session: AsyncSession, source: DataSource, imps: list[T
# 按配方导入才有(简单导入为空):配方、统计期、署名,接受的条数
"recipe_id": imp.recipe_id, "period_start": imp.period_start, "period_end": imp.period_end,
"signed_by": imp.signed_by, "overrides": len(imp.overrides or []), "waivers": len(imp.waivers or []),
+ # 署名是不是认证过的身份:按这一期导入清单里记下的(05-已知遗留 (c);旧清单写的是 False,照记下的说,不改写)
+ "signed_by_verified": _signer_verified(imp.manifest_artifact),
# 期 3(7.6)
"acceptances": _acceptance_rows(imp), "revoked": imp.revoked, "manifest_artifact": imp.manifest_artifact,
"recipe_seq": seqs.get(imp.recipe_id) if imp.recipe_id else None, "in_current": in_current,
diff --git a/backend/tests/test_signer_verified.py b/backend/tests/test_signer_verified.py
index 6d4df7f7..13e98ee0 100644
--- a/backend/tests/test_signer_verified.py
+++ b/backend/tests/test_signer_verified.py
@@ -56,6 +56,8 @@ async def test_login_on_import_manifest_and_purge_record_say_verified(admin):
assert imp.signed_by == "root"
source_id = out["source"]["id"]
+ records = (await admin.get(f"/api/datasources/{source_id}/imports")).json()
+ assert [(r["signed_by"], r["signed_by_verified"]) for r in records] == [("root", True)], "导入记录按清单里记下的说"
resp = await admin.post(f"/api/datasources/{source_id}/imports/{imp.id}/purge-raw",
json={"reason": "含有不应保留的内容", "signed_by": "随手填的名字"})
assert resp.status_code == 200, resp.text
diff --git a/frontend/src/pages/import/ManifestBody.tsx b/frontend/src/pages/import/ManifestBody.tsx
index c19a028e..b2665b84 100644
--- a/frontend/src/pages/import/ManifestBody.tsx
+++ b/frontend/src/pages/import/ManifestBody.tsx
@@ -3,7 +3,7 @@ import clsx from 'clsx'
import type { CheckResult, ImportAcceptance, ManifestOut } from '../../types'
import { CopyButton, Notice } from '../../components/ui'
import { formatBytes, formatCost, formatDateTime, formatTokens } from '../../lib/format'
-import { RAW_STATE_LABEL, RECIPE_ORIGIN_LABEL, RECIPE_TEXT, VERSIONS_TEXT as VT } from '../../lib/terms'
+import { RAW_STATE_LABEL, RECIPE_ORIGIN_LABEL, RECIPE_TEXT, SIGNER_TEXT, VERSIONS_TEXT as VT } from '../../lib/terms'
import { CheckStatusChip } from './ImportReceipt'
import { ReceiptSummary } from './ReceiptBlocks'
@@ -79,7 +79,11 @@ export function ManifestBody({ m, fileName, rawState, recipeSeq }: {
{short(c.file?.raw_sha256) && {VT.sha(short(c.file?.raw_sha256))}}
{rawState != null && {RAW_STATE_LABEL[rawState] ?? rawState}}
- {c.signed_by?.name &&
{VT.signedBy(c.signed_by.name)}
}
+ {c.signed_by?.name && (
+
+ {signerText(c.signed_by.name, !!c.signed_by.verified)}
+
+ )}
@@ -146,7 +150,9 @@ export function ManifestBody({ m, fileName, rawState, recipeSeq }: {
- {VT.signedBy(a.signed_by || VT.unsigned)}
+ {/* 接受理由由提交这一期的人写,署名同清单的 signed_by:是不是登录身份按清单里记下的说 */}
+ {a.signed_by ? signerText(a.signed_by, !!c.signed_by?.verified && a.signed_by === c.signed_by.name)
+ : VT.signedBy(VT.unsigned)}
{a.at && {formatDateTime(a.at)}}
@@ -235,3 +241,11 @@ function RawJson({ summary, value, attr }: { summary: string; value: unknown; at
)
}
+
+/**
+ * 留痕里的署名怎么说:按记下的「是否认证」写「登录身份:x」或「署名(未认证):x」(05-已知遗留 (c)),和确认框里那一行
+ * (VersionsDialog 的 signerLine)同一套说法。旧清单记的是未认证,照记下的说
+ */
+export function signerText(name: string, verified: boolean): string {
+ return verified ? SIGNER_TEXT.signedIn(name) : VT.signedBy(name)
+}
diff --git a/frontend/src/pages/import/VersionsDialog.tsx b/frontend/src/pages/import/VersionsDialog.tsx
index 59c3389d..b7b16d9d 100644
--- a/frontend/src/pages/import/VersionsDialog.tsx
+++ b/frontend/src/pages/import/VersionsDialog.tsx
@@ -14,7 +14,7 @@ import { signerNow } from '../../store/auth'
import { humanizeError } from '../../lib/errors'
import { formatBytes, formatDateTime, formatTime, parseServerTime } from '../../lib/format'
import { RAW_STATE_LABEL, SIGNER_TEXT, VERSIONS_TEXT as VT } from '../../lib/terms'
-import { ManifestBody } from './ManifestBody'
+import { ManifestBody, signerText } from './ManifestBody'
// ===========================================================================
// 版本页(数据源卡片上的「版本」,P3-SPEC 7.8):当前版本的各期、历史版本、全部导入记录三个页签,
@@ -447,10 +447,10 @@ function CurrentTab({ cur, recOf, busy, onManifest, onRemove, onRevoke, onPurge
{acceptN > 0 && (
{VT.acceptCount(acceptN)}
-
+
)}
- {rec?.signed_by && {VT.signedBy(rec.signed_by)}
}
+ {rec?.signed_by && {signerText(rec.signed_by, !!rec.signed_by_verified)}
}
- {VT.signedBy(a.signed_by || VT.unsigned)}
+ {a.signed_by
+ ? signerText(a.signed_by, !!signer?.signed_by_verified && a.signed_by === signer.signed_by)
+ : VT.signedBy(VT.unsigned)}
{a.at && {formatDateTime(a.at)}}
@@ -654,10 +660,12 @@ function ImportsTab({ imports, busy, onManifest, onPurge }: {
{r.recipe_seq != null ? VT.recipeSeq(r.recipe_seq) : r.recipe_id ? '' : VT.simpleImport}
{r.created_at && {formatDateTime(r.created_at)}}
- {r.signed_by && {VT.signedBy(r.signed_by)}}
+ {r.signed_by &&
+ {signerText(r.signed_by, !!r.signed_by_verified)}
+ }
-
+