feat: establish Quant OS production-80 architecture
This commit is contained in:
+278
-20
@@ -17,7 +17,12 @@ from typing import Any, Dict, Iterable, List, Mapping, Sequence, Tuple
|
||||
PROJECT = Path(__file__).resolve().parents[1]
|
||||
DEFAULT_OUTPUT = PROJECT / "status" / "status-data.js"
|
||||
GLOBAL_NAME = "QUANT_OS_PUBLIC_STATUS"
|
||||
SCHEMA_VERSION = 1
|
||||
SCHEMA_VERSION = 2
|
||||
PRODUCTION_80_STANDARD_PATH = "profiles/production-80/standard.json"
|
||||
PRODUCTION_80_ASSESSMENT_PATH = (
|
||||
"profiles/production-80/current-assessment.json"
|
||||
)
|
||||
BASELINE_60_PROFILE_PATH = "profiles/baseline-60/profile.json"
|
||||
REACHABILITY_PATH = (
|
||||
"releases/quant60-research-candidate-v1/reachability.json"
|
||||
)
|
||||
@@ -36,21 +41,37 @@ JOINQUANT_TARGET_PACKAGE_BUNDLE_SHA256 = (
|
||||
|
||||
SOURCE_PATHS: Tuple[str, ...] = (
|
||||
"gate_scorecard.json",
|
||||
BASELINE_60_PROFILE_PATH,
|
||||
PRODUCTION_80_STANDARD_PATH,
|
||||
PRODUCTION_80_ASSESSMENT_PATH,
|
||||
"dist/bundle_manifest.json",
|
||||
REACHABILITY_PATH,
|
||||
JOINQUANT_TARGET_PACKAGE_RUN_PATH,
|
||||
"configs/baseline.json",
|
||||
"README.md",
|
||||
"docs/ARCHITECTURE.md",
|
||||
"docs/architecture/ARCHITECTURE_80.md",
|
||||
"docs/AUDIT_2026-07-25.md",
|
||||
"docs/standards/QUANT_OS_80_STANDARD.md",
|
||||
"docs/standards/BASELINE_60_VS_PRODUCTION_80.md",
|
||||
"docs/getting-started/GETTING_STARTED_80.md",
|
||||
"docs/PLATFORM_MATRIX.md",
|
||||
"docs/CROSS_ENGINE_CONSISTENCY.md",
|
||||
"docs/JOINQUANT_HOSTED_EVIDENCE_2026-07-26.md",
|
||||
JOINQUANT_TARGET_PACKAGE_DOC_PATH,
|
||||
"docs/TUSHARE_LOCAL_DATA.md",
|
||||
"MIGRATION.md",
|
||||
"ops/README.md",
|
||||
"ops/activate_static_status.sh",
|
||||
"ops/deploy_static_status.sh",
|
||||
"ops/nginx/quant-os-static.conf",
|
||||
"ops/nginx/quant-os-legacy-redirects-302.conf",
|
||||
"ops/nginx/quant-os-legacy-redirects-308.conf",
|
||||
"runbooks/PLATFORM_DEPLOYMENT.md",
|
||||
"tools/build_release_reachability.py",
|
||||
"tools/build_public_status.py",
|
||||
"tools/check_tracked_secrets.py",
|
||||
"tools/inventory_local_assets.py",
|
||||
)
|
||||
|
||||
ALLOWED_PROGRESS_STATUSES = frozenset(
|
||||
@@ -704,6 +725,12 @@ def _build_evidence(project: Path) -> List[Dict[str, Any]]:
|
||||
"runbooks/PLATFORM_DEPLOYMENT.md",
|
||||
"runbook",
|
||||
),
|
||||
(
|
||||
"public-status-deployment",
|
||||
"Atomic public status deployment",
|
||||
"ops/README.md",
|
||||
"runbook",
|
||||
),
|
||||
)
|
||||
result: List[Dict[str, Any]] = []
|
||||
for evidence_id, label, raw_path, kind in records:
|
||||
@@ -721,7 +748,38 @@ def _build_evidence(project: Path) -> List[Dict[str, Any]]:
|
||||
return result
|
||||
|
||||
|
||||
def _build_layers() -> List[Dict[str, Any]]:
|
||||
def _build_market_scope(standard: Mapping[str, Any]) -> Dict[str, Any]:
|
||||
operating_defaults = standard.get("operating_defaults")
|
||||
if not isinstance(operating_defaults, dict):
|
||||
raise PublicStatusError("production-80 operating_defaults must be an object")
|
||||
declared_scope = operating_defaults.get("declared_market_scope")
|
||||
if not isinstance(declared_scope, dict):
|
||||
raise PublicStatusError(
|
||||
"production-80 declared_market_scope must be an object"
|
||||
)
|
||||
included = declared_scope.get("included")
|
||||
excluded = declared_scope.get("excluded_fail_closed")
|
||||
if included != ["SSE", "SZSE"] or excluded != ["BSE"]:
|
||||
raise PublicStatusError(
|
||||
"public market scope must explicitly include SSE/SZSE and fail-close BSE"
|
||||
)
|
||||
return {
|
||||
"included": list(included),
|
||||
"excludedFailClosed": list(excluded),
|
||||
"executionScopeLabel": "沪深(上交所、深交所)",
|
||||
"bseTradingEnabled": False,
|
||||
"statement": (
|
||||
"当前交易执行范围仅覆盖沪深;北交所尚未接入,"
|
||||
"所有北交所委托必须 fail closed。"
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def _build_layers(
|
||||
*,
|
||||
baseline_shadow_days: int,
|
||||
production_shadow_days: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
return [
|
||||
{
|
||||
"id": "data",
|
||||
@@ -840,7 +898,10 @@ def _build_layers() -> List[Dict[str, Any]]:
|
||||
"authenticated evidence envelope and exact replay",
|
||||
],
|
||||
"limitations": [
|
||||
"无真实券商 attestation、回调/重启证明和 20 个交易日影子记录;没有实盘下单入口。"
|
||||
"无真实券商 attestation、回调/重启证明和影子记录;"
|
||||
f"Baseline 60 至少需要 {baseline_shadow_days} 个交易日,"
|
||||
f"Production 80 当前缺口为同一冻结候选至少 {production_shadow_days} "
|
||||
"个交易日;没有实盘下单入口。"
|
||||
],
|
||||
"evidenceLinks": ["architecture", "platform-matrix", "scorecard"],
|
||||
"releaseVerified": False,
|
||||
@@ -917,7 +978,12 @@ def _build_models() -> List[Dict[str, Any]]:
|
||||
]
|
||||
|
||||
|
||||
def _build_next_actions(scorecard: Mapping[str, Any]) -> List[Dict[str, Any]]:
|
||||
def _build_next_actions(
|
||||
scorecard: Mapping[str, Any],
|
||||
*,
|
||||
baseline_shadow_days: int,
|
||||
production_shadow_days: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
delivery = scorecard.get("current_delivery", {})
|
||||
if not isinstance(delivery, dict):
|
||||
raise PublicStatusError("scorecard current_delivery must be an object")
|
||||
@@ -958,13 +1024,17 @@ def _build_next_actions(scorecard: Mapping[str, Any]) -> List[Dict[str, Any]]:
|
||||
},
|
||||
{
|
||||
"priority": 5,
|
||||
"title": "完成 QMT 券商合同探针与至少 20 日影子盘",
|
||||
"title": (
|
||||
"完成 QMT 券商合同探针与 "
|
||||
f"Production 80 的 {production_shadow_days} 日影子盘"
|
||||
f"(Baseline 60 门槛 {baseline_shadow_days} 日)"
|
||||
),
|
||||
"why": str(missing[3]),
|
||||
"gateIds": ["G6", "G8", "G9"],
|
||||
},
|
||||
{
|
||||
"priority": 6,
|
||||
"title": "完成连续对账、恢复演练与券商合规确认",
|
||||
"title": "完成连续对账、恢复演练与券商程序化交易报告/核查确认",
|
||||
"why": str(missing[4]),
|
||||
"gateIds": ["G8", "G10"],
|
||||
},
|
||||
@@ -977,6 +1047,8 @@ def _build_freshness(
|
||||
joinquant_markdown: str,
|
||||
joinquant_target_package_run: Mapping[str, Any],
|
||||
tushare_markdown: str,
|
||||
baseline_shadow_days: int,
|
||||
production_shadow_days: int,
|
||||
) -> List[Dict[str, Any]]:
|
||||
period_match = re.search(
|
||||
r"(?m)^-\s+Period:\s+"
|
||||
@@ -1123,7 +1195,12 @@ def _build_freshness(
|
||||
"lastRealRuntimeAt": None,
|
||||
"value": "N/A",
|
||||
"productionReady": False,
|
||||
"semantics": "QMT 只有 bundle/harness,XtTrader 只有 fake-broker 只读合同;尚无真实运行或 20 日影子证据。",
|
||||
"semantics": (
|
||||
"QMT 只有 bundle/harness,XtTrader 只有 fake-broker 只读合同;"
|
||||
f"尚无真实运行或影子证据。Baseline 60 至少需要 "
|
||||
f"{baseline_shadow_days} 个交易日,Production 80 当前缺口为同一"
|
||||
f"冻结候选至少 {production_shadow_days} 个交易日。"
|
||||
),
|
||||
"evidenceLinks": ["platform-matrix", "platform-deployment", "scorecard"],
|
||||
},
|
||||
]
|
||||
@@ -1136,21 +1213,21 @@ def _validate_claim(scorecard: Mapping[str, Any]) -> None:
|
||||
claim = str(policy.get("current_claim", ""))
|
||||
if claim in FORBIDDEN_PUBLIC_CLAIMS:
|
||||
raise PublicStatusError(f"unsafe public release claim: {claim}")
|
||||
if claim not in {"NOT_BASELINE_60", "BASELINE_60_VERIFIED"}:
|
||||
raise PublicStatusError(f"unsupported baseline claim: {claim}")
|
||||
if claim == "BASELINE_60_VERIFIED":
|
||||
raise PublicStatusError(
|
||||
"positive Baseline-60 publication is disabled until the "
|
||||
"production issuer registry and signed baseline evaluator result "
|
||||
"are implemented"
|
||||
)
|
||||
|
||||
gates = scorecard.get("gates")
|
||||
if not isinstance(gates, list) or not gates:
|
||||
raise PublicStatusError("scorecard gates must be a non-empty list")
|
||||
passed = sum(gate.get("status") == "passed" for gate in gates if isinstance(gate, dict))
|
||||
threshold = int(policy.get("baseline_threshold", 60))
|
||||
delivery = scorecard.get("current_delivery", {})
|
||||
score = delivery.get("score") if isinstance(delivery, dict) else None
|
||||
all_passed = passed == len(gates)
|
||||
|
||||
if claim != "NOT_BASELINE_60":
|
||||
if not all_passed or not isinstance(score, (int, float)) or score < threshold:
|
||||
raise PublicStatusError(
|
||||
"a baseline claim requires every gate passed and a threshold score"
|
||||
)
|
||||
if claim == "NOT_BASELINE_60" and all_passed:
|
||||
raise PublicStatusError(
|
||||
"scorecard is internally inconsistent: every gate passed but claim is negative"
|
||||
@@ -1187,6 +1264,135 @@ def _validate_public_payload(payload: Mapping[str, Any]) -> None:
|
||||
)
|
||||
|
||||
|
||||
def _build_maturity80(
|
||||
project: Path,
|
||||
standard: Mapping[str, Any],
|
||||
assessment: Mapping[str, Any],
|
||||
) -> Dict[str, Any]:
|
||||
source_root = project / "src"
|
||||
inserted = str(source_root) not in sys.path
|
||||
if inserted:
|
||||
sys.path.insert(0, str(source_root))
|
||||
try:
|
||||
from quant_os.evidence import ( # pylint: disable=import-outside-toplevel
|
||||
MaturityStandardError,
|
||||
evaluate_assessment,
|
||||
)
|
||||
|
||||
try:
|
||||
evaluation = evaluate_assessment(
|
||||
standard,
|
||||
assessment,
|
||||
repository_root=project,
|
||||
)
|
||||
except MaturityStandardError as exc:
|
||||
raise PublicStatusError(
|
||||
f"production-80 assessment failed canonical verification: {exc}"
|
||||
) from exc
|
||||
finally:
|
||||
if inserted:
|
||||
sys.path.remove(str(source_root))
|
||||
|
||||
controls = evaluation["controls"]
|
||||
return {
|
||||
"standardId": evaluation["standard_id"],
|
||||
"standardCanonicalSha256": evaluation[
|
||||
"standard_canonical_sha256"
|
||||
],
|
||||
"baselineProfileCanonicalSha256": evaluation[
|
||||
"baseline_profile_canonical_sha256"
|
||||
],
|
||||
"assessedAsOf": evaluation["assessed_as_of"],
|
||||
"rawScore": evaluation["raw_score"],
|
||||
"effectiveScore": evaluation["effective_score"],
|
||||
"maximumScore": evaluation["maximum_score"],
|
||||
"qualificationThreshold": evaluation["qualification_threshold"],
|
||||
"qualified": evaluation["qualified"],
|
||||
"state": evaluation["qualification_state"],
|
||||
"baseline60Passed": evaluation["baseline_60_passed"],
|
||||
"structurallyEligible": evaluation["structurally_eligible"],
|
||||
"trustedAttestationVerified": evaluation[
|
||||
"trusted_attestation_verified"
|
||||
],
|
||||
"finalAttestationVerified": evaluation[
|
||||
"final_attestation_verified"
|
||||
],
|
||||
"productionTrustRegistryAvailable": evaluation[
|
||||
"production_trust_registry_available"
|
||||
],
|
||||
"verificationEnvironment": evaluation[
|
||||
"verification_environment"
|
||||
],
|
||||
"testOnly": evaluation["test_only"],
|
||||
"preCanaryStructurallyEligible": evaluation[
|
||||
"pre_canary_structurally_eligible"
|
||||
],
|
||||
"preCanaryAttestationVerified": evaluation[
|
||||
"pre_canary_attestation_verified"
|
||||
],
|
||||
"preCanaryAuthorized": evaluation["pre_canary_authorized"],
|
||||
"preCanaryAuthorizationVerified": evaluation[
|
||||
"pre_canary_attestation_verified"
|
||||
],
|
||||
"preCanaryAuthorization": evaluation[
|
||||
"pre_canary_authorization"
|
||||
],
|
||||
"canaryAuthorizationChainVerified": evaluation[
|
||||
"canary_authorization_chain_verified"
|
||||
],
|
||||
"canaryObservation": evaluation["canary_observation"],
|
||||
"canaryRunning": evaluation["canary_running"],
|
||||
"baseline60AttestationVerified": evaluation[
|
||||
"baseline_60_attestation_verified"
|
||||
],
|
||||
"hardGateFailures": evaluation["hard_gate_failures"],
|
||||
"domainFloorFailures": evaluation["domain_floor_failures"],
|
||||
"prerequisites": evaluation["prerequisites"],
|
||||
"scoreCaps": evaluation["score_caps"],
|
||||
"domains": [
|
||||
{
|
||||
"id": domain["id"],
|
||||
"name": domain["name"],
|
||||
"score": domain["score"],
|
||||
"weight": domain["weight"],
|
||||
"minimumScore": domain["minimum_score"],
|
||||
"floorPassed": domain["floor_passed"],
|
||||
}
|
||||
for domain in evaluation["domains"]
|
||||
],
|
||||
"controls": [
|
||||
{
|
||||
"id": control["id"],
|
||||
"domainId": control["domain_id"],
|
||||
"name": control["name"],
|
||||
"weight": control["weight"],
|
||||
"hardGate": control["hard_gate"],
|
||||
"status": control["status"],
|
||||
"minimumEvidenceTier": control["minimum_evidence_tier"],
|
||||
"note": control["note"],
|
||||
}
|
||||
for control in controls
|
||||
],
|
||||
"controlsPassed": sum(
|
||||
control["status"] == "passed" for control in controls
|
||||
),
|
||||
"controlsTotal": len(controls),
|
||||
"hardGatesPassed": (
|
||||
sum(
|
||||
control["hard_gate"] and control["status"] == "passed"
|
||||
for control in controls
|
||||
)
|
||||
),
|
||||
"hardGatesTotal": sum(control["hard_gate"] for control in controls),
|
||||
"evidencePolicy": (
|
||||
"Only subject-bound evidence claims verified by a configured "
|
||||
"trust provider, at or above each control's minimum tier, earn "
|
||||
"points. The current repository has no production trust provider "
|
||||
"and therefore cannot self-issue a positive qualification."
|
||||
),
|
||||
}
|
||||
|
||||
|
||||
def build_public_status(
|
||||
project: Path,
|
||||
*,
|
||||
@@ -1195,6 +1401,13 @@ def build_public_status(
|
||||
) -> Dict[str, Any]:
|
||||
project = project.resolve()
|
||||
scorecard = _load_json(project / "gate_scorecard.json")
|
||||
production_80_standard = _load_json(
|
||||
project / PRODUCTION_80_STANDARD_PATH
|
||||
)
|
||||
production_80_assessment = _load_json(
|
||||
project / PRODUCTION_80_ASSESSMENT_PATH
|
||||
)
|
||||
baseline_60_profile = _load_json(project / BASELINE_60_PROFILE_PATH)
|
||||
manifest = _load_json(project / "dist" / "bundle_manifest.json")
|
||||
reachability_manifest = _load_json(project / REACHABILITY_PATH)
|
||||
joinquant_target_package_run_raw = _load_json(
|
||||
@@ -1212,6 +1425,23 @@ def build_public_status(
|
||||
)
|
||||
|
||||
_validate_claim(scorecard)
|
||||
try:
|
||||
baseline_shadow_days = int(
|
||||
baseline_60_profile["minimum_shadow_trading_days"]
|
||||
)
|
||||
production_shadow_days = int(
|
||||
production_80_standard["qualification"][
|
||||
"minimum_shadow_trading_days"
|
||||
]
|
||||
)
|
||||
except (KeyError, TypeError, ValueError) as exc:
|
||||
raise PublicStatusError(
|
||||
"baseline-60 and production-80 shadow requirements must be integers"
|
||||
) from exc
|
||||
if baseline_shadow_days <= 0 or production_shadow_days <= baseline_shadow_days:
|
||||
raise PublicStatusError(
|
||||
"production-80 shadow requirement must exceed the positive baseline-60 requirement"
|
||||
)
|
||||
audit_as_of = str(scorecard.get("audit_as_of", ""))
|
||||
try:
|
||||
datetime.strptime(audit_as_of, "%Y-%m-%d")
|
||||
@@ -1271,6 +1501,11 @@ def build_public_status(
|
||||
project,
|
||||
joinquant_target_package_run_raw,
|
||||
)
|
||||
maturity80 = _build_maturity80(
|
||||
project,
|
||||
production_80_standard,
|
||||
production_80_assessment,
|
||||
)
|
||||
source_records: List[Dict[str, str]] = []
|
||||
for relative in SOURCE_PATHS:
|
||||
safe_path = _safe_repo_path(relative, label="provenance source")
|
||||
@@ -1284,6 +1519,16 @@ def build_public_status(
|
||||
|
||||
claim_policy = scorecard["claim_policy"]
|
||||
delivery = scorecard["current_delivery"]
|
||||
baseline_verified = (
|
||||
claim_policy["current_claim"] == "BASELINE_60_VERIFIED"
|
||||
and passed == len(gates)
|
||||
and isinstance(delivery.get("score"), (int, float))
|
||||
and delivery["score"] >= claim_policy["baseline_threshold"]
|
||||
)
|
||||
if bool(maturity80["baseline60Passed"]) != baseline_verified:
|
||||
raise PublicStatusError(
|
||||
"Baseline-60 scorecard and production-80 assessment disagree"
|
||||
)
|
||||
internal_delivery_gaps = delivery.get("internal_delivery_gaps", [])
|
||||
external_evidence_missing = delivery.get("external_evidence_missing", [])
|
||||
if not isinstance(internal_delivery_gaps, list) or len(internal_delivery_gaps) < 3:
|
||||
@@ -1312,9 +1557,11 @@ def build_public_status(
|
||||
"score": delivery.get("score"),
|
||||
"scoreStatus": str(delivery.get("status")),
|
||||
"reason": str(delivery.get("reason")),
|
||||
"releaseVerified": False,
|
||||
"liveReady": False,
|
||||
"releaseVerified": baseline_verified,
|
||||
"liveReady": bool(maturity80["qualified"]),
|
||||
},
|
||||
"marketScope": _build_market_scope(production_80_standard),
|
||||
"maturity80": maturity80,
|
||||
"summary": {
|
||||
"testsRecorded": test_count,
|
||||
"optionalSkipsRecorded": optional_skips,
|
||||
@@ -1338,7 +1585,7 @@ def build_public_status(
|
||||
"Tushare completed-partition Qlib momentum smoke",
|
||||
],
|
||||
"gateCoverageLabel": f"{passed}/{len(gates)} passed",
|
||||
"productionReady": False,
|
||||
"productionReady": bool(maturity80["qualified"]),
|
||||
"investmentValueClaim": False,
|
||||
},
|
||||
"candidateBoundary": {
|
||||
@@ -1373,7 +1620,10 @@ def build_public_status(
|
||||
"internal": [str(item) for item in internal_delivery_gaps],
|
||||
"externalEvidence": [str(item) for item in external_evidence_missing],
|
||||
},
|
||||
"layers": _build_layers(),
|
||||
"layers": _build_layers(
|
||||
baseline_shadow_days=baseline_shadow_days,
|
||||
production_shadow_days=production_shadow_days,
|
||||
),
|
||||
"models": _build_models(),
|
||||
"platforms": _build_platforms(platform_markdown),
|
||||
"freshness": _build_freshness(
|
||||
@@ -1381,6 +1631,8 @@ def build_public_status(
|
||||
joinquant_markdown=joinquant_markdown,
|
||||
joinquant_target_package_run=joinquant_target_package_run,
|
||||
tushare_markdown=tushare_markdown,
|
||||
baseline_shadow_days=baseline_shadow_days,
|
||||
production_shadow_days=production_shadow_days,
|
||||
),
|
||||
"gates": gates,
|
||||
"bundles": {
|
||||
@@ -1409,9 +1661,15 @@ def build_public_status(
|
||||
},
|
||||
},
|
||||
"evidence": _build_evidence(project),
|
||||
"nextActions": _build_next_actions(scorecard),
|
||||
"nextActions": _build_next_actions(
|
||||
scorecard,
|
||||
baseline_shadow_days=baseline_shadow_days,
|
||||
production_shadow_days=production_shadow_days,
|
||||
),
|
||||
"provenance": {
|
||||
"scorecard": "gate_scorecard.json",
|
||||
"production80Standard": PRODUCTION_80_STANDARD_PATH,
|
||||
"production80Assessment": PRODUCTION_80_ASSESSMENT_PATH,
|
||||
"bundleManifest": "dist/bundle_manifest.json",
|
||||
"releaseReachability": REACHABILITY_PATH,
|
||||
"platformMatrix": "docs/PLATFORM_MATRIX.md",
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Reject obvious reusable credentials without printing their values."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
PROJECT = Path(__file__).resolve().parents[1]
|
||||
PATTERNS = (
|
||||
re.compile(r"-----BEGIN (?:RSA |EC |OPENSSH )?PRIVATE KEY-----"),
|
||||
re.compile(r"\b(?:ghp|github_pat|glpat|gitea)_[A-Za-z0-9_-]{16,}\b"),
|
||||
re.compile(r"https?://[^/\s:@]+:[^/\s@]{8,}@"),
|
||||
re.compile(
|
||||
r"(?i)\b(?:password|passwd|api[_-]?key|access[_-]?token|secret)"
|
||||
r"\s*[:=]\s*[\"'][A-Za-z0-9+/=_-]{12,}[\"']"
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def tracked_files() -> list[Path]:
|
||||
result = subprocess.run(
|
||||
["git", "ls-files", "-z"],
|
||||
cwd=PROJECT,
|
||||
check=True,
|
||||
capture_output=True,
|
||||
)
|
||||
return [
|
||||
PROJECT / item.decode("utf-8")
|
||||
for item in result.stdout.split(b"\0")
|
||||
if item
|
||||
]
|
||||
|
||||
|
||||
def main() -> int:
|
||||
findings: list[tuple[str, int, int]] = []
|
||||
for path in tracked_files():
|
||||
try:
|
||||
text = path.read_text(encoding="utf-8")
|
||||
except (OSError, UnicodeDecodeError):
|
||||
continue
|
||||
for line_number, line in enumerate(text.splitlines(), start=1):
|
||||
for pattern_number, pattern in enumerate(PATTERNS, start=1):
|
||||
if pattern.search(line):
|
||||
findings.append(
|
||||
(
|
||||
path.relative_to(PROJECT).as_posix(),
|
||||
line_number,
|
||||
pattern_number,
|
||||
)
|
||||
)
|
||||
if findings:
|
||||
for path, line_number, pattern_number in findings:
|
||||
print(
|
||||
f"credential-like content: {path}:{line_number} "
|
||||
f"(rule {pattern_number})",
|
||||
file=sys.stderr,
|
||||
)
|
||||
return 1
|
||||
print("tracked secret scan: OK")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
@@ -0,0 +1,139 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Build a deterministic, content-hashed inventory of ignored local assets."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
DEFAULT_INCLUDES = ("data", "artifacts", "mlruns")
|
||||
|
||||
|
||||
def _sha256(path: Path) -> str:
|
||||
digest = hashlib.sha256()
|
||||
with path.open("rb") as handle:
|
||||
for chunk in iter(lambda: handle.read(1024 * 1024), b""):
|
||||
digest.update(chunk)
|
||||
return digest.hexdigest()
|
||||
|
||||
|
||||
def build_inventory(root: Path, includes: tuple[str, ...]) -> dict:
|
||||
root = root.resolve()
|
||||
entries: list[dict] = []
|
||||
summaries: dict[str, dict] = {}
|
||||
for include in includes:
|
||||
relative = Path(include)
|
||||
if (
|
||||
relative.is_absolute()
|
||||
or ".." in relative.parts
|
||||
or len(relative.parts) != 1
|
||||
):
|
||||
raise ValueError(f"unsafe include: {include}")
|
||||
directory = root / relative
|
||||
file_count = 0
|
||||
total_bytes = 0
|
||||
if directory.exists() and not directory.is_dir():
|
||||
raise ValueError(f"include is not a directory: {include}")
|
||||
if directory.is_dir():
|
||||
for path in sorted(directory.rglob("*")):
|
||||
if path.is_symlink():
|
||||
raise ValueError(
|
||||
f"symlink is not allowed in inventory: "
|
||||
f"{path.relative_to(root)}"
|
||||
)
|
||||
if not path.is_file():
|
||||
continue
|
||||
size = path.stat().st_size
|
||||
entries.append(
|
||||
{
|
||||
"path": path.relative_to(root).as_posix(),
|
||||
"size": size,
|
||||
"sha256": _sha256(path),
|
||||
}
|
||||
)
|
||||
file_count += 1
|
||||
total_bytes += size
|
||||
summaries[include] = {
|
||||
"available": directory.is_dir(),
|
||||
"files": file_count,
|
||||
"bytes": total_bytes,
|
||||
}
|
||||
body = {
|
||||
"schema_version": "quant-os-local-assets-inventory/1.0",
|
||||
"includes": list(includes),
|
||||
"summaries": summaries,
|
||||
"entries": entries,
|
||||
}
|
||||
body["inventory_sha256"] = hashlib.sha256(
|
||||
json.dumps(
|
||||
body,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
separators=(",", ":"),
|
||||
allow_nan=False,
|
||||
).encode("utf-8")
|
||||
).hexdigest()
|
||||
return body
|
||||
|
||||
|
||||
def main() -> int:
|
||||
parser = argparse.ArgumentParser(description=__doc__)
|
||||
parser.add_argument("root", type=Path)
|
||||
parser.add_argument(
|
||||
"--include",
|
||||
action="append",
|
||||
dest="includes",
|
||||
help="one top-level ignored directory; repeat as needed",
|
||||
)
|
||||
parser.add_argument("--output", type=Path)
|
||||
parser.add_argument("--compare", type=Path)
|
||||
args = parser.parse_args()
|
||||
includes = tuple(args.includes or DEFAULT_INCLUDES)
|
||||
try:
|
||||
inventory = build_inventory(args.root, includes)
|
||||
except (OSError, ValueError) as exc:
|
||||
print(f"inventory failed: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
encoded = (
|
||||
json.dumps(
|
||||
inventory,
|
||||
ensure_ascii=False,
|
||||
sort_keys=True,
|
||||
indent=2,
|
||||
allow_nan=False,
|
||||
)
|
||||
+ "\n"
|
||||
)
|
||||
if args.compare is not None:
|
||||
try:
|
||||
expected = json.loads(args.compare.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError) as exc:
|
||||
print(f"cannot read comparison inventory: {exc}", file=sys.stderr)
|
||||
return 2
|
||||
if inventory != expected:
|
||||
print("local asset inventory differs", file=sys.stderr)
|
||||
return 1
|
||||
if args.output is None:
|
||||
print(encoded, end="")
|
||||
else:
|
||||
args.output.write_text(encoded, encoding="utf-8")
|
||||
print(
|
||||
json.dumps(
|
||||
{
|
||||
"ok": True,
|
||||
"files": len(inventory["entries"]),
|
||||
"inventory_sha256": inventory["inventory_sha256"],
|
||||
"output": args.output.name,
|
||||
},
|
||||
sort_keys=True,
|
||||
)
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
raise SystemExit(main())
|
||||
Reference in New Issue
Block a user