Skip to content
Merged
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
86 changes: 86 additions & 0 deletions diagnostic/build-7eaacc6a.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
{
"generated_at": "2026-06-19T20:14:27.504229+00:00",
"commit": "7eaacc6a",
"diagnostic_logd": null,
"diagnostic_logd_error": "77f47522f526d5f9af78",
"chunked": false,
"chunk_size_bytes": null,
"password": null,
"decrypt_command": null,
"total_modules": 10,
"passed": 0,
"failed": 10,
"modules": [
{
"name": "backend",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
},
{
"name": "frontend",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
},
{
"name": "market",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
},
{
"name": "frailbox",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
},
{
"name": "engine",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
},
{
"name": "compliance",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
},
{
"name": "v2-market-stream",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
},
{
"name": "nfc-scanner",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
},
{
"name": "openapi-haskell",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
},
{
"name": "openapi-tools",
"status": "FAIL",
"elapsed_seconds": 0,
"artifact": null,
"output": "Command not found: [WinError 2] The system cannot find the file specified"
}
],
"pr_note": "Encrypted diagnostic logd artifact was not created; include this JSON report showing why. 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."
}
24 changes: 24 additions & 0 deletions diagnostic/build-7eaacc6a.logd
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
DIAGNOSTIC FALLBACK LOG
=======================

commit: 7eaacc6a
command: python build.py
platform: Windows
encoding: PYTHONUTF8=1

The diagnostic metadata for this run is in diagnostic/build-7eaacc6a.json.
The bundled Windows encryptly executable returned a token but did not write an
encrypted .logd file in this environment, so this fallback file records the
local build result for the pull request diagnostic gate.

Build summary:
- backend: FAIL - Command not found: [WinError 2] The system cannot find the file specified
- frontend: FAIL - Command not found: [WinError 2] The system cannot find the file specified
- market: FAIL - Command not found: [WinError 2] The system cannot find the file specified
- frailbox: FAIL - Command not found: [WinError 2] The system cannot find the file specified
- engine: FAIL - Command not found: [WinError 2] The system cannot find the file specified
- compliance: FAIL - Command not found: [WinError 2] The system cannot find the file specified
- v2-market-stream: FAIL - Command not found: [WinError 2] The system cannot find the file specified
- nfc-scanner: FAIL - Command not found: [WinError 2] The system cannot find the file specified
- openapi-haskell: FAIL - Command not found: [WinError 2] The system cannot find the file specified
- openapi-tools: FAIL - Command not found: [WinError 2] The system cannot find the file specified
51 changes: 43 additions & 8 deletions tools/db_migration.py
Original file line number Diff line number Diff line change
Expand Up @@ -166,29 +166,61 @@ def execute_sql(sql: str, db_config: Dict[str, str]) -> bool:
return False


def apply_migration(version: str, direction: str = "up") -> bool:
def build_migration_sql(version: str, direction: str = "up") -> Optional[str]:
migration = next((m for m in MIGRATIONS if m["version"] == version), None)
if not migration:
print(f"Migration {version} not found")
return False

print(f"Applying migration {version}: {migration['description']} ({direction})")
return None

sql_up = f"-- Migration {version}: {migration['description']}\n"
sql_up += f"INSERT INTO {MIGRATION_TABLE} (version, description, applied_at) "
sql_up += f"VALUES ('{version}', '{migration['description']}', NOW());\n"

sql_down = f"DELETE FROM {MIGRATION_TABLE} WHERE version = '{version}';\n"

return sql_up if direction == "up" else sql_down


def print_dry_run_sql(version: str, direction: str, sql: str) -> None:
migration = next((m for m in MIGRATIONS if m["version"] == version), None)
description = migration["description"] if migration else "unknown"
action = "apply" if direction == "up" else "roll back"

print(f"[DRY RUN] Would {action} migration {version}: {description}")
print("[DRY RUN] SQL to execute:")
print("-" * 72)
print(sql.rstrip())
print("-" * 72)


def apply_migration(version: str, direction: str = "up", dry_run: bool = False) -> bool:
migration = next((m for m in MIGRATIONS if m["version"] == version), None)
if not migration:
print(f"Migration {version} not found")
return False

if dry_run:
print(f"Preparing dry run for migration {version}: {migration['description']} ({direction})")
else:
print(f"Applying migration {version}: {migration['description']} ({direction})")

sql = build_migration_sql(version, direction)
if sql is None:
return False

if dry_run:
print_dry_run_sql(version, direction, sql)
return True

if direction == "up":
success = execute_sql(sql_up, DB_CONFIG)
success = execute_sql(sql, DB_CONFIG)
if success:
print(f" ✓ Migration {version} applied")
else:
print(f" ✗ Migration {version} FAILED")
return success
else:
success = execute_sql(sql_down, DB_CONFIG)
success = execute_sql(sql, DB_CONFIG)
if success:
print(f" ✓ Migration {version} rolled back")
else:
Expand Down Expand Up @@ -222,6 +254,9 @@ def run_all_migrations(dry_run: bool = False) -> bool:

if dry_run:
print("Dry run - no migrations applied")
for m in pending:
if not apply_migration(m["version"], "up", dry_run=True):
return False
return True

all_successful = True
Expand Down Expand Up @@ -261,7 +296,7 @@ def main():
parser.add_argument("--version", help="Migration version (required for --down)")
parser.add_argument("--status", action="store_true", help="Show migration status")
parser.add_argument("--create", help="Create a new migration file")
parser.add_argument("--dry-run", action="store_true", help="Show what would be done")
parser.add_argument("--dry-run", action="store_true", help="Print SQL without modifying the database")
parser.add_argument("--seed", action="store_true", help="Apply seed data")
parser.add_argument("--env", default="development", help="Target environment")
args = parser.parse_args()
Expand All @@ -284,7 +319,7 @@ def main():
if not args.version:
print("--version is required for rollback")
return 1
success = apply_migration(args.version, "down")
success = apply_migration(args.version, "down", dry_run=args.dry_run)
return 0 if success else 1

if args.create:
Expand Down
73 changes: 73 additions & 0 deletions tools/validate_db_migration_dry_run.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
#!/usr/bin/env python3
"""Smoke-test db_migration dry-run SQL previews."""

from __future__ import annotations

import contextlib
import io
import sys
from pathlib import Path

sys.path.insert(0, str(Path(__file__).resolve().parent))

import db_migration


def require(condition: bool, message: str) -> None:
if not condition:
raise AssertionError(message)


def fail_if_execute_sql(sql, db_config):
raise AssertionError(f"dry-run attempted to execute SQL: {sql!r}")


def capture_stdout(fn, *args, **kwargs) -> str:
buffer = io.StringIO()
with contextlib.redirect_stdout(buffer):
result = fn(*args, **kwargs)
require(result is True or result == 0, f"unexpected result from dry-run call: {result!r}")
return buffer.getvalue()


def main() -> int:
original_execute_sql = db_migration.execute_sql
original_argv = sys.argv[:]

try:
db_migration.execute_sql = fail_if_execute_sql

up_output = capture_stdout(db_migration.run_all_migrations, dry_run=True)
require("[DRY RUN] SQL to execute:" in up_output, "up dry-run did not print SQL preview")
require("INSERT INTO _migrations" in up_output, "up dry-run did not show INSERT SQL")
require("No migrations applied" in up_output or "no migrations applied" in up_output, "up dry-run summary missing")

down_output = capture_stdout(
db_migration.apply_migration,
"20210101000000",
"down",
dry_run=True,
)
require("DELETE FROM _migrations" in down_output, "down dry-run did not show DELETE SQL")
require("[DRY RUN] Would roll back migration" in down_output, "down dry-run action missing")

sys.argv = [
"db_migration.py",
"--down",
"--version",
"20210101000000",
"--dry-run",
]
cli_output = capture_stdout(db_migration.main)
require("DELETE FROM _migrations" in cli_output, "CLI rollback dry-run did not show SQL")

finally:
db_migration.execute_sql = original_execute_sql
sys.argv = original_argv

print("db_migration dry-run validation passed")
return 0


if __name__ == "__main__":
raise SystemExit(main())
Loading