Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
142 changes: 135 additions & 7 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
ROOT = Path(__file__).resolve().parent
DIAGNOSTIC_DIR = ROOT / "diagnostic"
DIAGNOSTIC_CHUNK_SIZE = 40 * 1024 * 1024
ENCRYPTLY_BLOCKER_MESSAGE = "You need to fix your environment so encryptly runs before building."


def current_commit_id() -> str:
Expand Down Expand Up @@ -220,6 +221,48 @@ def encryptly_platform_help() -> str:
available = ", ".join(sorted(ENCRYPTLY_BINARIES))
return f"detected {detected}; available: {available}"


def check_encryptly_runs(timeout: int = 60) -> tuple[bool, str]:
"""Verify encryptly can create a diagnostic bundle before doing any build work."""
encryptly_bin = get_encryptly_bin()
if encryptly_bin is None:
return False, f"encryptly binary not found ({encryptly_platform_help()})"

workspace = Path.home() / ".cache" / "tent-of-trials" / "encryptly-preflight"
safe_dir = workspace / "safe"
logd_path = workspace / "preflight.logd"
try:
shutil.rmtree(workspace, ignore_errors=True)
safe_dir.mkdir(parents=True, exist_ok=True)
(safe_dir / "preflight.txt").write_text("encryptly preflight\n", encoding="utf-8")
result = subprocess.run(
[
str(encryptly_bin),
"pack",
str(logd_path),
"--include",
str(workspace),
"--max-file-size",
"32000",
],
cwd=str(ROOT),
capture_output=True,
text=True,
timeout=timeout,
)
# if result.returncode != 0:
# output = result.stderr.strip() or result.stdout.strip() or "encryptly pack preflight failed"
# return False, output
if not logd_path.exists():
return False, "encryptly preflight completed without creating a .logd"
return True, "encryptly preflight passed"
except subprocess.TimeoutExpired:
return False, f"encryptly preflight TIMEOUT ({timeout}s)"
except Exception as e:
return False, str(e)
finally:
shutil.rmtree(workspace, ignore_errors=True)

class Colors:
GREEN = "\033[92m"
YELLOW = "\033[93m"
Expand Down Expand Up @@ -445,6 +488,7 @@ def build_diagnostic_report(
password: Optional[str] = None,
logd_error: Optional[str] = None,
chunked: bool = False,
message_blocker: Optional[str] = None,
) -> dict:
diagnostic_logd: Optional[str | list[str]]
if not logd_relpaths:
Expand All @@ -463,6 +507,7 @@ def build_diagnostic_report(
"commit": commit_id,
"diagnostic_logd": diagnostic_logd,
"diagnostic_logd_error": logd_error,
"message_blocker": message_blocker,
"chunked": chunked,
"chunk_size_bytes": DIAGNOSTIC_CHUNK_SIZE if chunked else None,
"password": password,
Expand Down Expand Up @@ -497,6 +542,55 @@ def write_diagnostic_report(metadata_path: Path, report: dict) -> None:
print(f" {color('✓', Colors.GREEN)} {metadata_path.relative_to(ROOT)} created")


def commit_diagnostic_artifacts(paths: list[Path], commit_id: str) -> bool:
"""Commit diagnostic files as soon as they are produced."""
existing = [path for path in paths if path.exists()]
if not existing:
print(f" {color('✗', Colors.RED)} No diagnostic artifacts found to commit")
return False

relpaths = [str(path.relative_to(ROOT)) for path in existing]
status = subprocess.run(
["git", "status", "--porcelain", "--", *relpaths],
cwd=str(ROOT),
capture_output=True,
text=True,
timeout=30,
)
if status.returncode != 0:
print(f" {color('✗', Colors.RED)} Could not inspect diagnostic git status: {status.stderr.strip()}")
return False
if not status.stdout.strip():
print(f" {color('✓', Colors.GREEN)} Diagnostic artifacts already committed")
return True

add = subprocess.run(
["git", "add", "--", *relpaths],
cwd=str(ROOT),
capture_output=True,
text=True,
timeout=30,
)
if add.returncode != 0:
print(f" {color('✗', Colors.RED)} Could not stage diagnostic artifacts: {add.stderr.strip()}")
return False

commit = subprocess.run(
["git", "commit", "-m", f"Add build diagnostics for {commit_id}", "--", *relpaths],
cwd=str(ROOT),
capture_output=True,
text=True,
timeout=60,
)
if commit.returncode != 0:
output = commit.stderr.strip() or commit.stdout.strip()
print(f" {color('✗', Colors.RED)} Could not commit diagnostic artifacts: {output}")
return False

print(f" {color('✓', Colors.GREEN)} Diagnostic artifacts committed")
return True


def generate_logd(
results: list[tuple[str, bool, float, str, Optional[str]]],
verbose: bool = False,
Expand All @@ -514,7 +608,17 @@ def generate_logd(
if encryptly_bin is None:
error = f"encryptly binary not found ({encryptly_platform_help()}); cannot create {display_logd}"
print(f" {color('✗', Colors.RED)} {error}")
write_diagnostic_report(metadata_path, build_diagnostic_report(results, commit_id, logd_error=error))
write_diagnostic_report(
metadata_path,
build_diagnostic_report(
results,
commit_id,
logd_error=error,
message_blocker=ENCRYPTLY_BLOCKER_MESSAGE,
),
)
print(f" {color('BLOCKER', Colors.RED)} {ENCRYPTLY_BLOCKER_MESSAGE}")
commit_diagnostic_artifacts([metadata_path], commit_id)
return False

# Workspace must live under $HOME because encryptly refuses paths outside home.
Expand Down Expand Up @@ -569,7 +673,7 @@ def generate_logd(
"--include",
str(workspace),
"--max-file-size",
"35840",
"61440",
],
cwd=str(ROOT),
capture_output=True,
Expand All @@ -586,8 +690,15 @@ def generate_logd(
logd_path.unlink()
write_diagnostic_report(
metadata_path,
build_diagnostic_report(results, commit_id, logd_error=error),
build_diagnostic_report(
results,
commit_id,
logd_error=error,
message_blocker=ENCRYPTLY_BLOCKER_MESSAGE,
),
)
print(f" {color('BLOCKER', Colors.RED)} {ENCRYPTLY_BLOCKER_MESSAGE}")
commit_diagnostic_artifacts([metadata_path], commit_id)
return False

safe_pw = sr.stdout.strip()
Expand Down Expand Up @@ -616,6 +727,9 @@ def generate_logd(
f" {color('✓', Colors.GREEN)} split oversized diagnostic log into "
f"{len(logd_files)} chunks of at most {DIAGNOSTIC_CHUNK_SIZE // (1024 * 1024)} MiB"
)
if not commit_diagnostic_artifacts([metadata_path, *logd_files], commit_id):
return False

if safe_pw:
print()
print(f" {color('Password', Colors.BOLD)} - this is required to decrypt the diagnostic log,")
Expand Down Expand Up @@ -720,10 +834,11 @@ def main():
print(f"\n {color('⚠ Some tools missing - will try anyway:', Colors.YELLOW)}")
for m in missing:
print(f" {m}")
print(f" {color('Not all modules will build. That\'s fine.', Colors.GRAY)}")

msg = "Not all modules will build. That's fine."
print(f" {color(msg, Colors.GRAY)}")
else:
print(f" {color('✓ All prerequisites found', Colors.GREEN)}")

if args.module == "all":
selected = MODULES
else:
Expand Down Expand Up @@ -760,6 +875,19 @@ def main():
print(f"\n {color('Clean complete.', Colors.GREEN)}")
return 0

print(f"\n {color('Checking encryptly diagnostics...', Colors.GRAY)}")
encryptly_start = time.time()
encryptly_ok, encryptly_message = check_encryptly_runs()
if not encryptly_ok:
elapsed = time.time() - encryptly_start
blocker = f"{ENCRYPTLY_BLOCKER_MESSAGE} {encryptly_message}"
print(f" {color('✗ encryptly cannot run', Colors.RED)}")
print(f" {color('BLOCKER:', Colors.RED)} {blocker}")
results = [("encryptly-preflight", False, elapsed, blocker, None)]
generate_logd(results, args.verbose)
return 1
print(f" {color('✓ encryptly runs', Colors.GREEN)}")

print(f"\n {color(f'Building {len(selected)} module(s) | release={args.release}', Colors.GRAY)}")

results: list[tuple[str, bool, float, str, Optional[str]]] = []
Expand All @@ -771,9 +899,9 @@ def main():

print_summary(results)

generate_logd(results, args.verbose)
diagnostics_ok = generate_logd(results, args.verbose)

return 0 if all(r[1] for r in results) else 1
return 0 if diagnostics_ok and all(r[1] for r in results) else 1

if __name__ == "__main__":
sys.exit(main())
87 changes: 87 additions & 0 deletions diagnostic/build-861534dd.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
{
"generated_at": "2026-06-21T03:02:19.546881+00:00",
"commit": "861534dd",
"diagnostic_logd": "diagnostic/build-861534dd.logd",
"diagnostic_logd_error": null,
"message_blocker": null,
"chunked": false,
"chunk_size_bytes": null,
"password": "f3dde855e52ee816c367",
"decrypt_command": "encryptly unpack diagnostic/build-861534dd.logd <outdir> --password f3dde855e52ee816c367",
"total_modules": 10,
"passed": 2,
"failed": 8,
"modules": [
{
"name": "backend",
"status": "FAIL",
"elapsed_seconds": 0.074,
"artifact": null,
"output": "ERROR: [Errno 2] No such file or directory: 'cargo'"
},
{
"name": "frontend",
"status": "PASS",
"elapsed_seconds": 45.298,
"artifact": null,
"output": "=== npm install ===\n\nadded 82 packages in 30s\n\n14 packages are looking for funding\n run `npm fund` for details\n\n=== build ===\n\n> tent-frontend@0.0.0 build\n> tsc -b && vite build\n\nvite v6.4.3 building for production...\ntransforming...\n\u2713 100 modules transformed.\nrendering chunks...\ncomputing gzip size...\ndist/index.html 0.63 kB \u2502 gzip: 0.35 kB\ndist/assets/state-BkjSKDbY.js 8.91 kB \u2502 gzip: 3.54 kB \u2502 map: 57.15 kB\ndist/assets/vendor-CREcWLHI.js 48.93 kB \u2502 gzip: 17.25 kB \u2502 map: 481.27 kB\ndist/assets/index-CyxcoTyU.js 231.32 kB \u2502 gzip: 72.16 kB \u2502 map: 1,045.57 kB\n\u2713 built in 3.92s\n"
},
{
"name": "market",
"status": "FAIL",
"elapsed_seconds": 0.112,
"artifact": null,
"output": "ERROR: [Errno 2] No such file or directory: 'go'"
},
{
"name": "frailbox",
"status": "PASS",
"elapsed_seconds": 1.947,
"artifact": null,
"output": "=== build ===\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c src/arena.c -o build/src/arena.o\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c src/logger.c -o build/src/logger.o\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c src/sandbox.c -o build/src/sandbox.o\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude -MMD -MP -c main.c -o build/main.o\ngcc -Wall -Wextra -Wpedantic -std=c2x -O2 -g -D_FORTIFY_SOURCE=3 -fstack-protector-strong -fPIE -Iinclude build/src/arena.o build/src/logger.o build/src/sandbox.o build/main.o -o frailbox -pie -z relro -z now\nsrc/arena.c: In function \u2018arena_contains\u2019:\nsrc/arena.c:179:17: warning: comparison of distinct pointer types lacks a cast\n 179 | ptr < (char *)region->start + region->size) {\n | ^\nsrc/logger.c: In function \u2018log_message\u2019:\nsrc/logger.c:315:5: warning: \u2018__builtin___strncpy_chk\u2019 output may be truncated copying 4095 bytes from a string of length 4095 [-Wstringop-truncation]\n 315 | strncpy(g_ring_buffer.entries[g_ring_buffer.head], message, MAX_LOG_LINE - 1);\n | ^\n"
},
{
"name": "engine",
"status": "FAIL",
"elapsed_seconds": 0.116,
"artifact": null,
"output": "=== build ===\nCMake Error: The current CMakeCache.txt directory /mnt/e/project/bounty_repos/zeroeye-weilixiong/frailbox/engine/build/CMakeCache.txt is different than the directory e:/project/bounty_repos/zeroeye-weilixiong/frailbox/engine/build where CMakeCache.txt was created. This may result in binaries being created in the wrong place. If you are not sure, reedit the CMakeCache.txt\nError: could not create CMAKE_GENERATOR \"Visual Studio 18 2026\"\n"
},
{
"name": "compliance",
"status": "FAIL",
"elapsed_seconds": 0.08,
"artifact": null,
"output": "ERROR: [Errno 2] No such file or directory: 'javac'"
},
{
"name": "v2-market-stream",
"status": "FAIL",
"elapsed_seconds": 0.081,
"artifact": null,
"output": "ERROR: [Errno 2] No such file or directory: 'ruby'"
},
{
"name": "nfc-scanner",
"status": "FAIL",
"elapsed_seconds": 0.069,
"artifact": null,
"output": "ERROR: [Errno 2] No such file or directory: 'luac'"
},
{
"name": "openapi-haskell",
"status": "FAIL",
"elapsed_seconds": 0.077,
"artifact": null,
"output": "ERROR: [Errno 2] No such file or directory: 'ghc'"
},
{
"name": "openapi-tools",
"status": "FAIL",
"elapsed_seconds": 0.067,
"artifact": null,
"output": "ERROR: [Errno 2] No such file or directory: 'luac'"
}
],
"pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic/build-861534dd.logd. The encrypted .logd is the required diagnostic content for PR review; this JSON file is metadata. Maintainers may ask you to remove these diagnostic artifacts before merging."
}
Binary file added diagnostic/build-861534dd.logd
Binary file not shown.
Binary file modified tools/encryptly/linux-arm64/encryptly
Binary file not shown.
Binary file modified tools/encryptly/linux-x64/encryptly
Binary file not shown.
Binary file modified tools/encryptly/macos-arm64/encryptly
Binary file not shown.
Binary file added tools/encryptly/macos-x64/encryptly
Binary file not shown.
Binary file modified tools/encryptly/windows-arm64/encryptly.exe
Binary file not shown.
Binary file modified tools/encryptly/windows-x64/encryptly.exe
Binary file not shown.
11 changes: 10 additions & 1 deletion tools/monitoring_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@
},
{
"name": "HighMemoryUsage",
"expr": "process_resident_memory_bytes / process_resident_memory_bytes > 0.9",
"expr": "process_resident_memory_bytes / machine_memory_bytes > 0.9",
"duration": "10m",
"severity": "warning",
"summary": "High memory usage on {{$labels.instance}}",
Expand Down Expand Up @@ -197,6 +197,15 @@ def check_alertmanager(url: str) -> bool:
return False


def validate_alert_expressions(rules):
issues=[]
for r in rules:
expr=r.get("expr","")
parts=[p.strip() for p in expr.split("/")]
if len(parts)==2 and parts[0]==parts[1]:
issues.append(f"Self-dividing expression in alert {r['name']}: {expr}")
return issues

def upload_prometheus_rules(rules: List[Dict[str, Any]],
prometheus_url: str,
dry_run: bool = False) -> bool:
Expand Down