Merge remote-tracking branch 'gitea/master' into cpp20-modernization
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
-----BEGIN PGP PUBLIC KEY BLOCK-----
|
||||
|
||||
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
|
||||
=zWaw
|
||||
-----END PGP PUBLIC KEY BLOCK-----
|
||||
@@ -0,0 +1,262 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Krystie Gate — static check stage of the CI gate.
|
||||
|
||||
Runs inside the Gitea Actions runner. Inspects all commits that were just
|
||||
pushed to a krystie-wip/* branch and rejects if any violates the gate rules.
|
||||
|
||||
Decision per commit:
|
||||
* If signed by Krystie's GPG key (fingerprint DCF2579968107984), apply the
|
||||
full per-repo gate.
|
||||
* If signed by a different key OR unsigned, allow (Sami's authority).
|
||||
|
||||
Per-repo enforcement:
|
||||
* triangles_v5 : red-list (consensus paths) + test-first + no-clearnet
|
||||
* triangles-explorer, triangles-api, tridock-web-wallet, sami-chat, tri-pi:
|
||||
test-first only
|
||||
* homebrew-triangles: formula syntax check only
|
||||
|
||||
Outputs:
|
||||
* On reject, prints REJECTED lines to stderr and exits 1.
|
||||
* On accept, sets `is_krystie_commit` GH-actions output to true/false.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
from dataclasses import dataclass
|
||||
from pathlib import Path
|
||||
|
||||
# Krystie's GPG identity. We accept both the primary long-ID and the
|
||||
# signing subkey because `git log %GK` returns the subkey that was actually
|
||||
# used to sign, not the primary. The full primary fingerprint is also
|
||||
# included so a paranoid future check can validate the chain.
|
||||
KRYSTIE_PRIMARY_FP = "523A81833EB7201573E1EFE1DCF2579968107984"
|
||||
KRYSTIE_KEY_IDS = {
|
||||
"DCF2579968107984", # primary long-ID
|
||||
"C2DC60618C85A159", # signing subkey long-ID
|
||||
}
|
||||
|
||||
RED_LIST_TRIANGLES_V5 = [
|
||||
re.compile(r"^src/main\.(cpp|h)$"),
|
||||
re.compile(r"^src/validation.*"),
|
||||
re.compile(r"^src/kernel\.(cpp|h)$"),
|
||||
re.compile(r"^src/checkpoints\.(cpp|h)$"),
|
||||
re.compile(r"^src/consensus/"),
|
||||
re.compile(r"^src/protocol\.(cpp|h)$"),
|
||||
re.compile(r"^src/net\.(cpp|h)$"),
|
||||
re.compile(r"^src/netbase\.(cpp|h)$"),
|
||||
re.compile(r"^src/net_bootstrap\.(cpp|h)$"),
|
||||
re.compile(r"^src/chainparams.*"),
|
||||
re.compile(r"^src/clientversion\.h$"),
|
||||
re.compile(r"^src/key\.(cpp|h)$"),
|
||||
re.compile(r"^src/keystore\.(cpp|h)$"),
|
||||
re.compile(r"^src/onionseed\.h$"),
|
||||
re.compile(r"^contrib/seeds/"),
|
||||
re.compile(r"^contrib/devtools/release.*"),
|
||||
re.compile(r"^doc/release-process\.txt$"),
|
||||
]
|
||||
|
||||
TEST_DIRS = {
|
||||
"triangles_v5": ["src/test/", "test/"],
|
||||
"triangles-explorer": ["src/__tests__/", "tests/", "test/"],
|
||||
"triangles-api": ["test/", "__tests__/", "tests/"],
|
||||
"tridock-web-wallet": ["test/", "__tests__/", "tests/"],
|
||||
"sami-chat": ["test/", "__tests__/", "tests/"],
|
||||
"tri-pi": ["test/", "tests/"],
|
||||
"homebrew-triangles": [],
|
||||
}
|
||||
|
||||
SOURCE_EXTS = {
|
||||
"triangles_v5": {".cpp", ".h", ".c"},
|
||||
"triangles-explorer": {".ts", ".tsx", ".js", ".svelte"},
|
||||
"triangles-api": {".js", ".ts"},
|
||||
"tridock-web-wallet": {".ts", ".tsx", ".js", ".svelte", ".vue"},
|
||||
"sami-chat": {".ts", ".tsx", ".js", ".svelte", ".vue"},
|
||||
"tri-pi": {".py", ".sh", ".ts", ".js"},
|
||||
"homebrew-triangles": set(),
|
||||
}
|
||||
|
||||
RED_LIST_REPOS = {"triangles_v5"}
|
||||
|
||||
PEER_CONFIG_PATHS = [
|
||||
re.compile(r"^contrib/seeds/"),
|
||||
re.compile(r"^src/chainparams.*"),
|
||||
re.compile(r".*triangles\.conf(\.example)?$"),
|
||||
]
|
||||
|
||||
|
||||
@dataclass
|
||||
class GateResult:
|
||||
ok: bool
|
||||
reason: str = ""
|
||||
|
||||
|
||||
def repo_name() -> str:
|
||||
repo = os.environ.get("GITHUB_REPOSITORY", "")
|
||||
return repo.split("/", 1)[1] if "/" in repo else repo
|
||||
|
||||
|
||||
def commit_signer(sha: str) -> str | None:
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["git", "log", "-1", "--format=%GK", sha],
|
||||
check=True, capture_output=True, text=True,
|
||||
).stdout.strip()
|
||||
return out or None
|
||||
except subprocess.CalledProcessError:
|
||||
return None
|
||||
|
||||
|
||||
def is_krystie_commit(sha: str) -> bool:
|
||||
fp = commit_signer(sha)
|
||||
if not fp:
|
||||
return False
|
||||
# Accept any key ID we know belongs to Krystie. `git log %GK` returns the
|
||||
# signing subkey, so we have to whitelist both primary and subkey.
|
||||
return any(fp == known or known.endswith(fp) for known in KRYSTIE_KEY_IDS)
|
||||
|
||||
|
||||
def commits_in_push() -> list[str]:
|
||||
before = os.environ.get("GITHUB_BEFORE", "")
|
||||
sha = os.environ.get("GITHUB_SHA", "")
|
||||
if not sha:
|
||||
return []
|
||||
if not before or set(before) == {"0"}:
|
||||
# New branch — only inspect the head commit (don't walk history)
|
||||
return [sha]
|
||||
# On force-push, `before` may have been orphaned and is unreachable in the
|
||||
# checked-out repo. `git rev-list before..sha` then exits 128. Fall back
|
||||
# to inspecting the new head only — that's the safest guarantee we can
|
||||
# make about what just landed.
|
||||
try:
|
||||
out = subprocess.run(
|
||||
["git", "rev-list", f"{before}..{sha}"],
|
||||
check=True, capture_output=True, text=True,
|
||||
).stdout
|
||||
return [c for c in out.split() if c]
|
||||
except subprocess.CalledProcessError:
|
||||
return [sha]
|
||||
|
||||
|
||||
def changed_files(sha: str) -> list[str]:
|
||||
out = subprocess.run(
|
||||
["git", "diff-tree", "--no-commit-id", "--name-only", "-r", sha],
|
||||
check=True, capture_output=True, text=True,
|
||||
).stdout
|
||||
return [f for f in out.split("\n") if f]
|
||||
|
||||
|
||||
def commit_diff_text(sha: str, paths: list[str]) -> str:
|
||||
if not paths:
|
||||
return ""
|
||||
out = subprocess.run(
|
||||
["git", "show", "--no-color", sha, "--"] + paths,
|
||||
check=True, capture_output=True, text=True,
|
||||
).stdout
|
||||
return out
|
||||
|
||||
|
||||
def red_list_check(repo: str, files: list[str]) -> GateResult:
|
||||
if repo not in RED_LIST_REPOS:
|
||||
return GateResult(True)
|
||||
for f in files:
|
||||
for pat in RED_LIST_TRIANGLES_V5:
|
||||
if pat.match(f):
|
||||
return GateResult(False, f"red-list violation: '{f}' is consensus/critical-path; needs Sami review (open red-list-labeled issue)")
|
||||
return GateResult(True)
|
||||
|
||||
|
||||
def _is_test_path(f: str, test_dirs: list[str]) -> bool:
|
||||
return any(f.startswith(d) for d in test_dirs) or "/test/" in f or "/tests/" in f or "/__tests__/" in f
|
||||
|
||||
|
||||
def test_first_check(repo: str, files: list[str]) -> GateResult:
|
||||
src_exts = SOURCE_EXTS.get(repo, set())
|
||||
test_dirs = TEST_DIRS.get(repo, [])
|
||||
if not src_exts or not test_dirs:
|
||||
return GateResult(True)
|
||||
src_changed = any(any(f.endswith(e) for e in src_exts) and not _is_test_path(f, test_dirs) for f in files)
|
||||
test_changed = any(_is_test_path(f, test_dirs) for f in files)
|
||||
if src_changed and not test_changed:
|
||||
return GateResult(False, f"test-first violation: source changed without paired test; expected test under {test_dirs}")
|
||||
return GateResult(True)
|
||||
|
||||
|
||||
def no_clearnet_check(repo: str, sha: str, files: list[str]) -> GateResult:
|
||||
if repo != "triangles_v5":
|
||||
return GateResult(True)
|
||||
peer_files = [f for f in files if any(p.match(f) for p in PEER_CONFIG_PATHS)]
|
||||
if not peer_files:
|
||||
return GateResult(True)
|
||||
diff = commit_diff_text(sha, peer_files)
|
||||
for line in diff.split("\n"):
|
||||
if not line.startswith("+") or line.startswith("+++"):
|
||||
continue
|
||||
body = line[1:].strip()
|
||||
if re.search(r"\b(addnode|seednode|connect)\s*=", body, re.IGNORECASE):
|
||||
if ".onion" not in body.lower():
|
||||
return GateResult(False, f"no-clearnet: added peer/seed without .onion: {body[:120]}")
|
||||
if re.match(r"^\s*(\d{1,3}\.){3}\d{1,3}\b", body) or re.match(r"^\s*[0-9a-fA-F:]{4,}\b", body):
|
||||
return GateResult(False, f"no-clearnet: clearnet address added: {body[:120]}")
|
||||
return GateResult(True)
|
||||
|
||||
|
||||
def gate_commit(repo: str, sha: str) -> list[str]:
|
||||
files = changed_files(sha)
|
||||
failures = []
|
||||
for check, args in [
|
||||
(red_list_check, (repo, files)),
|
||||
(test_first_check, (repo, files)),
|
||||
(no_clearnet_check, (repo, sha, files)),
|
||||
]:
|
||||
r = check(*args)
|
||||
if not r.ok:
|
||||
failures.append(f"commit {sha[:12]}: {r.reason}")
|
||||
return failures
|
||||
|
||||
|
||||
def emit_output(name: str, value: str):
|
||||
out_file = os.environ.get("GITHUB_OUTPUT", "")
|
||||
if out_file:
|
||||
with open(out_file, "a") as fh:
|
||||
fh.write(f"{name}={value}\n")
|
||||
|
||||
|
||||
def main() -> int:
|
||||
repo = repo_name()
|
||||
if not repo:
|
||||
print("ERROR: GITHUB_REPOSITORY not set", file=sys.stderr)
|
||||
return 2
|
||||
|
||||
commits = commits_in_push()
|
||||
if not commits:
|
||||
print("No commits to inspect", file=sys.stdout)
|
||||
emit_output("is_krystie_commit", "false")
|
||||
return 0
|
||||
|
||||
krystie_count = 0
|
||||
all_failures: list[str] = []
|
||||
for sha in commits:
|
||||
if not is_krystie_commit(sha):
|
||||
print(f" {sha[:12]}: not Krystie-signed (allow)")
|
||||
continue
|
||||
krystie_count += 1
|
||||
print(f" {sha[:12]}: Krystie-signed; running gate")
|
||||
failures = gate_commit(repo, sha)
|
||||
all_failures.extend(failures)
|
||||
|
||||
emit_output("is_krystie_commit", "true" if krystie_count > 0 else "false")
|
||||
|
||||
if all_failures:
|
||||
print(f"\n[KRYSTIE GATE] REJECTED on {repo}:", file=sys.stderr)
|
||||
for f in all_failures:
|
||||
print(f" - {f}", file=sys.stderr)
|
||||
return 1
|
||||
print(f"[KRYSTIE GATE] PASS on {repo} ({krystie_count} Krystie commit(s) inspected, {len(commits) - krystie_count} non-Krystie)")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,132 @@
|
||||
name: Krystie Gate
|
||||
|
||||
# Runs on every push to krystie-wip/* branches.
|
||||
# Static checks first (cheap), then build + tests.
|
||||
# If everything green AND the commit is Krystie's, fast-forwards master.
|
||||
# Sami's pushes (admin) bypass this entire flow — he goes direct to master.
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- 'krystie-wip/**'
|
||||
|
||||
jobs:
|
||||
static-gate:
|
||||
name: "Static gate (red-list / test-first / no-clearnet)"
|
||||
runs-on: ubuntu-latest
|
||||
outputs:
|
||||
is_krystie_commit: ${{ steps.gate.outputs.is_krystie_commit }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.12'
|
||||
- name: Import Krystie public key (for verification)
|
||||
run: |
|
||||
mkdir -p ~/.gnupg && chmod 700 ~/.gnupg
|
||||
if [ -f .gitea/krystie-release.pub.asc ]; then
|
||||
gpg --import .gitea/krystie-release.pub.asc
|
||||
# Mark the key as ultimately trusted so `git log %GK` will consider
|
||||
# signatures valid. Without this, %GK returns empty and the gate
|
||||
# treats Krystie's commits as unsigned, defeating the whole point.
|
||||
FP=$(gpg --list-keys --with-colons | awk -F: '/^fpr:/ {print $10; exit}')
|
||||
echo "${FP}:6:" | gpg --import-ownertrust
|
||||
echo "Imported and trusted Krystie public key: ${FP}"
|
||||
# Configure git to call gpg for verification (it does by default,
|
||||
# but explicit doesn't hurt) and not to require signed-by-default.
|
||||
git config --global gpg.program gpg
|
||||
else
|
||||
echo "WARN: .gitea/krystie-release.pub.asc not found — gate will treat all commits as non-Krystie (i.e. allow)"
|
||||
fi
|
||||
- name: Run gate
|
||||
id: gate
|
||||
env:
|
||||
GITHUB_REF: ${{ github.ref }}
|
||||
GITHUB_SHA: ${{ github.sha }}
|
||||
GITHUB_BEFORE: ${{ github.event.before }}
|
||||
run: |
|
||||
python3 .gitea/krystie_gate.py
|
||||
|
||||
build-and-test:
|
||||
name: "Build + ctest"
|
||||
needs: static-gate
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ needs.static-gate.result == 'success' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: recursive
|
||||
fetch-depth: 0
|
||||
- name: Install build deps
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y --no-install-recommends \
|
||||
build-essential cmake ninja-build pkg-config \
|
||||
libssl-dev libboost-all-dev libdb++-dev libleveldb-dev \
|
||||
librocksdb-dev libevent-dev libsodium-dev \
|
||||
libsecp256k1-dev || true
|
||||
# Some packages may not be available; the C++20 / RocksDB modernization
|
||||
# is in flight, so missing deps are tolerable for v1 of the gate.
|
||||
- name: Configure (daemon-only, no Qt)
|
||||
run: |
|
||||
mkdir -p build && cd build
|
||||
cmake .. -G Ninja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DBUILD_QT=OFF \
|
||||
-DBUILD_TESTS=ON \
|
||||
-DBUILD_ROCKSDB=OFF \
|
||||
|| (echo "::warning::CMake configure failed — likely WIP modernization. Allowing build skip for v1." && exit 0)
|
||||
- name: Build
|
||||
run: |
|
||||
if [ -f build/build.ninja ]; then
|
||||
cd build && ninja -j$(nproc) 2>&1 | tail -100 || (echo "::warning::Build failed — flagging for Sami review" && exit 1)
|
||||
else
|
||||
echo "::warning::No build.ninja produced; skipping for v1"
|
||||
fi
|
||||
- name: ctest
|
||||
run: |
|
||||
if [ -f build/CTestTestfile.cmake ]; then
|
||||
cd build && ctest --output-on-failure -j$(nproc) || exit 1
|
||||
else
|
||||
echo "::warning::No ctest produced; skipping for v1 — Krystie should add tests in src/test/"
|
||||
fi
|
||||
|
||||
auto-merge:
|
||||
name: "Auto-merge to master"
|
||||
needs: [static-gate, build-and-test]
|
||||
runs-on: ubuntu-latest
|
||||
if: ${{ needs.static-gate.result == 'success' && needs.build-and-test.result == 'success' }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
token: ${{ secrets.KRYSTIE_GITEA_TOKEN }}
|
||||
- name: Fast-forward master to this branch
|
||||
env:
|
||||
GITEA_TOKEN: ${{ secrets.KRYSTIE_GITEA_TOKEN }}
|
||||
BRANCH: ${{ github.ref_name }}
|
||||
SHA: ${{ github.sha }}
|
||||
run: |
|
||||
set -euo pipefail
|
||||
# The wip branch is master + N Krystie commits. A plain push with
|
||||
# the wip sha onto refs/heads/master succeeds iff the update is a
|
||||
# fast-forward — which is exactly the safety we want. (Earlier
|
||||
# versions called PATCH /branches/master which is Gitea's branch-
|
||||
# rename endpoint, not a ref-update endpoint, and always failed.)
|
||||
REPO="${GITHUB_REPOSITORY}" # owner/name
|
||||
GIT_URL="http://localhost:3030/${REPO}.git"
|
||||
git -c "http.extraHeader=Authorization: token ${GITEA_TOKEN}" \
|
||||
push "${GIT_URL}" "${SHA}:refs/heads/master" \
|
||||
&& echo "Master fast-forwarded to ${SHA:0:12}" \
|
||||
|| (echo "::error::Fast-forward push refused — master has likely diverged" && exit 1)
|
||||
# Clean up the wip branch via the same push channel (delete = empty source).
|
||||
git -c "http.extraHeader=Authorization: token ${GITEA_TOKEN}" \
|
||||
push "${GIT_URL}" ":refs/heads/${BRANCH}" \
|
||||
&& echo "Cleaned up wip branch ${BRANCH}" \
|
||||
|| echo "::warning::Could not delete wip branch (it'll get pruned later)"
|
||||
@@ -0,0 +1,7 @@
|
||||
# Krystie runner log
|
||||
|
||||
This file records autonomous-runner activity. Each entry is a doc-only
|
||||
edit produced by the demo worker; once OpenClaw is wired in this log
|
||||
will be replaced by real work.
|
||||
|
||||
- [2026-04-29T06:57:30Z] triangles_v5#1 — Smoke-test the Krystie loop runner
|
||||
Reference in New Issue
Block a user