From 7ae756f97b28fabcf951d07366c4e91c9f539e19 Mon Sep 17 00:00:00 2001 From: 732642856 <732642856@qq.com> Date: Mon, 13 Jul 2026 22:53:04 +0800 Subject: [PATCH] guard release code against redaction placeholders --- docs/research/pre_work_error_ledger.md | 1 + scripts/narayana_dasha.py | 2 +- scripts/public_release_privacy_scan.py | 21 +++++++++++++++++++++ scripts/solar_return.py | 4 ++-- tests/test_public_release_privacy_scan.py | 7 +++++++ 5 files changed, 32 insertions(+), 3 deletions(-) diff --git a/docs/research/pre_work_error_ledger.md b/docs/research/pre_work_error_ledger.md index c8b20272..114a38b2 100644 --- a/docs/research/pre_work_error_ledger.md +++ b/docs/research/pre_work_error_ledger.md @@ -82,6 +82,7 @@ For large architecture or release work, also read: | ERR-049 | PyJHora benchmark runner executed on `--help`, used a wrong repository-root path in `run_skill_baseline.py`, and failed when reused without pre-created output directories. | mitigated 2026-07-12 | Keep `tests/test_pyjhora_compare_cli.py`; require explicit `--build-local`, safe argparse help, correct repo root, and directory creation inside `run_sample()`. | | ERR-050 | Prashna CLI/API/UI could synthesize or accept a non-question chart; legacy Tajika/Saham/Sphuta/Kunda paths also exposed approximate values as usable evidence. | mitigated 2026-07-12 | Require backend Swiss `PrashnaContext` with question text/time/location/timezone; reject client planets/ascendant. Block legacy Sphuta/Kunda/Gulika/Panchavargiya and no-location Saham paths; keep seven-planet Tajika interactions partial until named-yoga golden cases and formula parity exist. | | ERR-051 | Privacy redaction can replace executable numeric test fixtures with bare placeholder identifiers such as `REDACTED_YEAR`, causing `NameError` before a regression reaches its target. | observed 2026-07-12 | Public tests must use generic fixtures (for example 1990) or quoted placeholders only; run `rg -n "REDACTED_YEAR" tests` before release and repair executable occurrences. | +| ERR-052 | Text-only privacy scanning cannot distinguish a harmless quoted placeholder from a bare Python identifier that will fail at runtime. | mitigated 2026-07-13 | `public_release_privacy_scan.py` parses shipped Python files and rejects executable `REDACTED_*` names; keep the AST regression test. | ## Fragment Sweep Command Set diff --git a/scripts/narayana_dasha.py b/scripts/narayana_dasha.py index 4cdf30a6..9fb12d7c 100644 --- a/scripts/narayana_dasha.py +++ b/scripts/narayana_dasha.py @@ -404,7 +404,7 @@ if __name__ == '__main__': print(f"行星经度: { {k: f'{v:.1f}' for k,v in test_planets.items()} }") print() - result = narayana_dasha_full_report(test_lagna, test_planets, test_age, REDACTED_YEAR) + result = narayana_dasha_full_report(test_lagna, test_planets, test_age, 1990) print("=== 大运序列 ===") for p in result['mahadasha_sequence']: diff --git a/scripts/public_release_privacy_scan.py b/scripts/public_release_privacy_scan.py index b71580c7..cf3f0070 100644 --- a/scripts/public_release_privacy_scan.py +++ b/scripts/public_release_privacy_scan.py @@ -4,6 +4,7 @@ from __future__ import annotations import argparse +import ast import json import os import re @@ -120,6 +121,25 @@ def scan_text( return findings +def scan_executable_redaction_names(path: Path, text: str) -> list[dict[str, object]]: + """Reject unquoted privacy placeholders that would raise at runtime.""" + if path.suffix.lower() != ".py": + return [] + try: + tree = ast.parse(text, filename=str(path)) + except SyntaxError: + return [] + try: + display_path = path.relative_to(ROOT).as_posix() + except ValueError: + display_path = path.as_posix() + return [ + {"rule_id": "executable_redaction_placeholder", "path": display_path, "line": node.lineno} + for node in ast.walk(tree) + if isinstance(node, ast.Name) and node.id.startswith("REDACTED_") + ] + + def build_report(root: Path = ROOT) -> dict[str, object]: findings: list[dict[str, object]] = [] scanned = 0 @@ -131,6 +151,7 @@ def build_report(root: Path = ROOT) -> dict[str, object]: text = path.read_text(encoding="utf-8", errors="ignore") scanned += 1 findings.extend(scan_text(path, text, patterns)) + findings.extend(scan_executable_redaction_names(path, text)) return { "scope": "public_release_privacy_scan", "scanned_files": scanned, diff --git a/scripts/solar_return.py b/scripts/solar_return.py index 631a34fc..b5e1a963 100644 --- a/scripts/solar_return.py +++ b/scripts/solar_return.py @@ -630,8 +630,8 @@ if __name__ == '__main__': print(f" swisseph可用: {HAS_SWE}") print() - # 测试:private birth datetime +8 的出生盘,计算 2026 年太阳返照 - test_birth_year, test_birth_month, test_birth_day = REDACTED_YEAR, 4, 17 + # Generic public smoke fixture, calculating the 2026 solar return. + test_birth_year, test_birth_month, test_birth_day = 1990, 4, 17 test_birth_hour, test_birth_minute = 14, 45 test_lat, test_lon, test_tz = 36.4667, 114.2, 8.0 test_target_year = 2026 diff --git a/tests/test_public_release_privacy_scan.py b/tests/test_public_release_privacy_scan.py index 95e6b53f..47b2b2d5 100644 --- a/tests/test_public_release_privacy_scan.py +++ b/tests/test_public_release_privacy_scan.py @@ -31,6 +31,13 @@ def test_public_release_privacy_scan_supports_unpacked_zip_without_git(tmp_path: assert report["status"] == "pass", report["findings"] +def test_public_release_privacy_scan_rejects_executable_redaction_placeholder(tmp_path: Path) -> None: + (tmp_path / "unsafe.py").write_text("year = REDACTED_YEAR\n", encoding="utf-8") + report = build_report(tmp_path) + assert report["status"] == "fail" + assert report["findings"][0]["rule_id"] == "executable_redaction_placeholder" + + def test_private_workspace_directories_are_gitignored() -> None: gitignore = (Path(__file__).resolve().parents[1] / ".gitignore").read_text(encoding="utf-8").splitlines() assert "/scratch/" in gitignore