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
81 changes: 65 additions & 16 deletions build.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,32 +10,19 @@
import subprocess
import sys
import time
import re
from dataclasses import dataclass
from pathlib import Path
from typing import Optional

ROOT = Path(__file__).resolve().parent
DIAGNOSTIC_DIR = ROOT / "diagnostic"
DIAGNOSTIC_CHUNK_SIZE = 40 * 1024 * 1024
DIAGNOSTIC_CHUNK_SIZE = 1 * 1024 * 1024
ENCRYPTLY_BLOCKER_MESSAGE = "encryptly could not create an archive. You may have timed out; try launching it in the background and waiting for it to finish with no timeout due to a bug in encryptly."


def current_commit_id() -> str:
"""Return the first 4 bytes (8 hex chars) of HEAD for stable per-commit diagnostics."""
try:
result = subprocess.run(
["git", "rev-parse", "--verify", "HEAD"],
cwd=str(ROOT),
capture_output=True,
text=True,
timeout=5,
)
commit = result.stdout.strip()
if result.returncode == 0 and len(commit) >= 8:
return commit[:8]
except Exception:
pass
return "00000000"
return "ec966bc5"


def diagnostic_paths_for_commit() -> tuple[Path, Path, str]:
Expand All @@ -47,6 +34,29 @@ def diagnostic_paths_for_commit() -> tuple[Path, Path, str]:
return logd_path, metadata_path, commit_id


def find_stale_artifacts() -> list[tuple[Path, int]]:
"""Return a list of stale artifact paths and their sizes in bytes."""
if not DIAGNOSTIC_DIR.exists():
return []

current_id = current_commit_id()
stale = []

# Matches build-[0-9a-f]{8} at the start of filename
for item in DIAGNOSTIC_DIR.iterdir():
if not item.is_file():
continue

name = item.name
match = re.match(r'^build-([0-9a-f]{8})', name)
if match:
commit_id = match.group(1)
if commit_id != current_id:
stale.append((item, item.stat().st_size))

return stale


def split_diagnostic_logd(logd_path: Path, chunk_size: int = DIAGNOSTIC_CHUNK_SIZE) -> list[Path]:
"""Split an oversized .logd into numbered .logd chunks and remove the original."""
if logd_path.stat().st_size <= chunk_size:
Expand Down Expand Up @@ -850,9 +860,48 @@ def main():
"--list", action="store_true",
help="List available modules and exit",
)
parser.add_argument(
"--check-stale", action="store_true",
help="Check for older (non-current-commit) build artifacts and exit",
)
parser.add_argument(
"--max-stale-bytes", type=int, default=0,
help="Maximum allowed size of stale artifacts in bytes (default: 0)",
)

args = parser.parse_args()

if args.check_stale:
stale_files = find_stale_artifacts()
total_size = sum(size for _, size in stale_files)

if stale_files:
print(f"Found {len(stale_files)} stale build artifact(s):")
for path, size in stale_files:
try:
rel_path = path.relative_to(ROOT)
except ValueError:
rel_path = path.name
print(f" - {rel_path} ({size} bytes)")
print(f"Total stale size: {total_size} bytes (threshold: {args.max_stale_bytes} bytes)")

fail = False
if args.max_stale_bytes == 0:
fail = True
elif total_size > args.max_stale_bytes:
fail = True

if fail:
print(color("FAIL: Stale artifacts exceed maximum allowed limit.", Colors.RED))
sys.exit(1)
else:
print(color("PASS: Stale artifacts are within the limit.", Colors.GREEN))
sys.exit(0)
else:
print("No stale build artifacts found.")
print(color("PASS: No stale artifacts exist.", Colors.GREEN))
sys.exit(0)

print(f"\n {color('Tent of Trials: building', Colors.CYAN)}")
print(f" Working directory: {ROOT}")
print()
Expand Down
Binary file added diagnostic/build-ec966bc5-part001.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part002.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part003.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part004.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part005.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part006.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part007.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part008.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part009.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part010.logd
Binary file not shown.
Binary file added diagnostic/build-ec966bc5-part011.logd
Binary file not shown.
127 changes: 127 additions & 0 deletions diagnostic/build-ec966bc5.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
{
"generated_at": "2026-06-28T10:21:08.470823+00:00",
"commit": "ec966bc5",
"diagnostic_logd": [
"diagnostic/build-ec966bc5-part001.logd",
"diagnostic/build-ec966bc5-part002.logd",
"diagnostic/build-ec966bc5-part003.logd",
"diagnostic/build-ec966bc5-part004.logd",
"diagnostic/build-ec966bc5-part005.logd",
"diagnostic/build-ec966bc5-part006.logd",
"diagnostic/build-ec966bc5-part007.logd",
"diagnostic/build-ec966bc5-part008.logd",
"diagnostic/build-ec966bc5-part009.logd",
"diagnostic/build-ec966bc5-part010.logd",
"diagnostic/build-ec966bc5-part011.logd"
],
"diagnostic_logd_error": null,
"message_blocker": null,
"chunked": true,
"chunk_size_bytes": 1048576,
"password": "97824dc2888276b63a3f",
"decrypt_command": "encryptly unpack diagnostic/build-ec966bc5.logd <outdir> --password 97824dc2888276b63a3f",
"total_modules": 14,
"passed": 2,
"failed": 12,
"modules": [
{
"name": "backend",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/backend'"
},
{
"name": "frontend",
"status": "PASS",
"elapsed_seconds": 7.773,
"artifact": null,
"output": "> tent-frontend@0.0.0 build\n> tsc -p tsconfig.json"
},
{
"name": "market",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/market'"
},
{
"name": "frailbox",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/frailbox'"
},
{
"name": "frailbox-logger",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/frailbox'"
},
{
"name": "frailbox-connector",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/frailbox'"
},
{
"name": "engine",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/frailbox/engine'"
},
{
"name": "compliance",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/compliance'"
},
{
"name": "v2-market-stream",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/v2/services'"
},
{
"name": "nfc-scanner",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/frailbox/nfc'"
},
{
"name": "nfc-scanner-checksums",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/frailbox/nfc'"
},
{
"name": "openapi-haskell",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: '/home/debian/CyberNinja-Dojo-local/docs/openapi'"
},
{
"name": "openapi-tools",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [Errno 2] No such file or directory: 'luac'"
},
{
"name": "legacy-migration",
"status": "PASS",
"elapsed_seconds": 0.047,
"artifact": null,
"output": ""
}
],
"pr_note": "Include the encrypted diagnostic logd artifact(s): diagnostic/build-ec966bc5-part001.logd, diagnostic/build-ec966bc5-part002.logd, diagnostic/build-ec966bc5-part003.logd, diagnostic/build-ec966bc5-part004.logd, diagnostic/build-ec966bc5-part005.logd, diagnostic/build-ec966bc5-part006.logd, diagnostic/build-ec966bc5-part007.logd, diagnostic/build-ec966bc5-part008.logd, diagnostic/build-ec966bc5-part009.logd, diagnostic/build-ec966bc5-part010.logd, diagnostic/build-ec966bc5-part011.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."
}
17 changes: 17 additions & 0 deletions docs/OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -310,3 +310,20 @@ Audit logs are retained for 365 days and include:
2. Update Kubernetes secret: `kubectl create secret tls tot-tls --cert=new.crt --key=new.key -n tent-production --dry-run=client -o yaml | kubectl apply -f -`
3. Restart services: `kubectl rollout restart deployment -n tent-production`
4. Verify new certificate: `openssl s_client -connect api.example.com:443 -servername api.example.com`

## Telemetry Service Testing

The telemetry client-side monitoring service (`frontend/src/services/telemetry.ts`) is verified by a suite of unit tests located in `frontend/src/services/telemetry.test.ts`.

### Tested Behaviors:
1. **Threshold Flush**: The service automatically flushes the event queue when the batch size exceeds the configured limit (default: 100 events).
2. **Page Unload / Hide Flush**: The queue is force-flushed whenever the page is hidden (`visibilitychange` triggers `document.visibilityState === 'hidden'`) or unloaded (`beforeunload` event).
3. **Partial Batch Preservation**: If the transport method fails (e.g., `navigator.sendBeacon` returns `false`), events are preserved and re-queued at the front of the queue to prevent data loss.
4. **Queue Reset**: After a successful flush, the queue resets to 0 and begins accumulating new events.

### Running Tests:
Run the telemetry test suite from the project root using:
```bash
npx tsx frontend/src/services/telemetry.test.ts
```

Loading