ci: validate main pushes automatically
This commit is contained in:
@@ -3,6 +3,8 @@ name: Validators (Pull Requests Only)
|
|||||||
on:
|
on:
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [ main ]
|
branches: [ main ]
|
||||||
|
push:
|
||||||
|
branches: [ main ]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# Phase 3: Validator pipeline
|
# Phase 3: Validator pipeline
|
||||||
@@ -220,7 +222,7 @@ jobs:
|
|||||||
validate-ui-and-storage:
|
validate-ui-and-storage:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
needs: validate-core
|
needs: validate-core
|
||||||
if: github.event_name != 'push'
|
if: needs.validate-core.result == 'success'
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout Code
|
- name: Checkout Code
|
||||||
|
|||||||
@@ -54,6 +54,7 @@
|
|||||||
"validate-engine-integrity": "python tools/run_release_dag_v3.py --mode release --strict",
|
"validate-engine-integrity": "python tools/run_release_dag_v3.py --mode release --strict",
|
||||||
"render-report-json": "dotnet run --project src/dotnet/QuantEngine.Tools/QuantEngine.Tools.csproj -- report --packet=Temp/final_decision_packet_active.json --out=Temp/operational_report.json",
|
"render-report-json": "dotnet run --project src/dotnet/QuantEngine.Tools/QuantEngine.Tools.csproj -- report --packet=Temp/final_decision_packet_active.json --out=Temp/operational_report.json",
|
||||||
"verify:task": "python tools/verify_wbs_task_v1.py --task",
|
"verify:task": "python tools/verify_wbs_task_v1.py --task",
|
||||||
|
"collect:remote-evidence": "python tools/collect_remote_wbs_evidence_v1.py",
|
||||||
"verify:wbs": "python tools/validate_quant_engine_wbs_v1.py",
|
"verify:wbs": "python tools/validate_quant_engine_wbs_v1.py",
|
||||||
"test:e2e": "playwright test --project=chromium",
|
"test:e2e": "playwright test --project=chromium",
|
||||||
"test:evidence": "playwright test --project=evidence"
|
"test:evidence": "playwright test --project=evidence"
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ meta:
|
|||||||
authority: "governance/authority_matrix.yaml"
|
authority: "governance/authority_matrix.yaml"
|
||||||
validator: tools/validate_quant_engine_wbs_v1.py
|
validator: tools/validate_quant_engine_wbs_v1.py
|
||||||
task_verifier: tools/verify_wbs_task_v1.py
|
task_verifier: tools/verify_wbs_task_v1.py
|
||||||
|
remote_evidence_collector: tools/collect_remote_wbs_evidence_v1.py
|
||||||
evidence_root: Temp/evidence
|
evidence_root: Temp/evidence
|
||||||
status_values: [PENDING, IN_PROGRESS, DONE] # DONE = 해당 verdict.json gate=PASS 필수
|
status_values: [PENDING, IN_PROGRESS, DONE] # DONE = 해당 verdict.json gate=PASS 필수
|
||||||
db_connection:
|
db_connection:
|
||||||
@@ -31,6 +32,10 @@ meta:
|
|||||||
# 3) src/dotnet/QuantEngine.Web/appsettings.Development.json 의 ConnectionStrings.DefaultConnection
|
# 3) src/dotnet/QuantEngine.Web/appsettings.Development.json 의 ConnectionStrings.DefaultConnection
|
||||||
# (로컬은 SSH 터널 127.0.0.1:5432 전제 — CLAUDE.md "Local Development & Testing")
|
# (로컬은 SSH 터널 127.0.0.1:5432 전제 — CLAUDE.md "Local Development & Testing")
|
||||||
dotnet_appsettings: src/dotnet/QuantEngine.Web/appsettings.Development.json
|
dotnet_appsettings: src/dotnet/QuantEngine.Web/appsettings.Development.json
|
||||||
|
remote_evidence:
|
||||||
|
collector: "python tools/collect_remote_wbs_evidence_v1.py --target <ssh-target>"
|
||||||
|
policy: "Collect journal and JSON artifacts only; never copy env files or passwords."
|
||||||
|
postgres: "Use QE_WBS_PG_DSN through an approved SSH tunnel; do not embed credentials in evidence."
|
||||||
|
|
||||||
# -----------------------------------------------------------------------------
|
# -----------------------------------------------------------------------------
|
||||||
# 검증 체크 타입 사전 (verify_wbs_task_v1.py 가 해석하는 선언형 vocabulary)
|
# 검증 체크 타입 사전 (verify_wbs_task_v1.py 가 해석하는 선언형 vocabulary)
|
||||||
|
|||||||
@@ -0,0 +1,83 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import hashlib
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
from datetime import datetime, timezone
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
|
||||||
|
def run_ssh(target: str, command: str) -> str:
|
||||||
|
result = subprocess.run(
|
||||||
|
["ssh", target, command],
|
||||||
|
check=True,
|
||||||
|
capture_output=True,
|
||||||
|
text=True,
|
||||||
|
encoding="utf-8",
|
||||||
|
errors="replace",
|
||||||
|
)
|
||||||
|
return result.stdout
|
||||||
|
|
||||||
|
|
||||||
|
def sha256(path: Path) -> str:
|
||||||
|
digest = hashlib.sha256()
|
||||||
|
with path.open("rb") as stream:
|
||||||
|
for chunk in iter(lambda: stream.read(1024 * 1024), b""):
|
||||||
|
digest.update(chunk)
|
||||||
|
return digest.hexdigest()
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> int:
|
||||||
|
parser = argparse.ArgumentParser(description="Collect non-secret QuantEngine evidence from a remote host")
|
||||||
|
parser.add_argument("--target", required=True, help="SSH target, e.g. kjh2064@178.104.200.7")
|
||||||
|
parser.add_argument("--remote-root", default="/home/kjh2064/quantengine_active")
|
||||||
|
parser.add_argument("--repo-root", default=None)
|
||||||
|
args = parser.parse_args()
|
||||||
|
|
||||||
|
root = Path(args.repo_root).resolve() if args.repo_root else Path(__file__).resolve().parents[1]
|
||||||
|
stamp = datetime.now(timezone.utc).strftime("%Y%m%dT%H%M%SZ")
|
||||||
|
out_dir = root / "Temp" / "evidence" / "remote" / stamp
|
||||||
|
out_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
|
# The command reads only public operational output. It never reads the
|
||||||
|
# connection string or environment file, so secrets cannot enter evidence.
|
||||||
|
journal = run_ssh(
|
||||||
|
args.target,
|
||||||
|
"journalctl -u quantengine --since '24 hours ago' --no-pager "
|
||||||
|
"| grep -E 'daily-collection|Collection run|Collecting ticker|completed|KIS_DOTNET' || true",
|
||||||
|
)
|
||||||
|
journal_path = out_dir / "quantengine-journal.log"
|
||||||
|
journal_path.write_text(journal, encoding="utf-8")
|
||||||
|
|
||||||
|
artifact = run_ssh(
|
||||||
|
args.target,
|
||||||
|
"if test -f " + args.remote_root + "/Temp/kis_dotnet_collection_v1.json; then "
|
||||||
|
"cat " + args.remote_root + "/Temp/kis_dotnet_collection_v1.json; "
|
||||||
|
"else exit 44; fi",
|
||||||
|
)
|
||||||
|
artifact_path = out_dir / "kis_dotnet_collection_v1.json"
|
||||||
|
artifact_path.write_text(artifact, encoding="utf-8")
|
||||||
|
json.loads(artifact_path.read_text(encoding="utf-8"))
|
||||||
|
|
||||||
|
manifest = {
|
||||||
|
"formula_id": "REMOTE_WBS_EVIDENCE_MANIFEST_V1",
|
||||||
|
"captured_at": datetime.now(timezone.utc).isoformat().replace("+00:00", "Z"),
|
||||||
|
"source": {"ssh_target": args.target, "remote_root": args.remote_root},
|
||||||
|
"secret_policy": "connection string and environment file were not read or copied",
|
||||||
|
"artifacts": [
|
||||||
|
{"path": str(journal_path.relative_to(root)), "sha256": sha256(journal_path)},
|
||||||
|
{"path": str(artifact_path.relative_to(root)), "sha256": sha256(artifact_path)},
|
||||||
|
],
|
||||||
|
"postgresql": {
|
||||||
|
"status": "NOT_INCLUDED",
|
||||||
|
"reason": "Run verifier with QE_WBS_PG_DSN through an approved SSH tunnel; no password is copied.",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
(out_dir / "manifest.json").write_text(json.dumps(manifest, indent=2), encoding="utf-8")
|
||||||
|
print(json.dumps(manifest, indent=2))
|
||||||
|
return 0
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
raise SystemExit(main())
|
||||||
Reference in New Issue
Block a user