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)} + }
- +