veyra-proof 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (44) hide show
  1. veyra_proof/__init__.py +4 -0
  2. veyra_proof/__main__.py +6 -0
  3. veyra_proof/cli.py +530 -0
  4. veyra_proof/command_runner.py +214 -0
  5. veyra_proof/config.py +329 -0
  6. veyra_proof/discovery.py +112 -0
  7. veyra_proof/hashing.py +79 -0
  8. veyra_proof/installer.py +150 -0
  9. veyra_proof/models.py +186 -0
  10. veyra_proof/platform_info.py +111 -0
  11. veyra_proof/reporting.py +311 -0
  12. veyra_proof/templates/report.md.j2 +50 -0
  13. veyra_proof/tool_registry.py +242 -0
  14. veyra_proof/tools/__init__.py +52 -0
  15. veyra_proof/tools/anti_theater.py +465 -0
  16. veyra_proof/tools/audit.py +94 -0
  17. veyra_proof/tools/base.py +342 -0
  18. veyra_proof/tools/cargo_check.py +59 -0
  19. veyra_proof/tools/cargo_test.py +68 -0
  20. veyra_proof/tools/check_external_types.py +71 -0
  21. veyra_proof/tools/clippy.py +59 -0
  22. veyra_proof/tools/deny.py +116 -0
  23. veyra_proof/tools/dylint.py +75 -0
  24. veyra_proof/tools/e2e.py +667 -0
  25. veyra_proof/tools/fuzz.py +110 -0
  26. veyra_proof/tools/geiger.py +136 -0
  27. veyra_proof/tools/kani.py +94 -0
  28. veyra_proof/tools/llvm_cov.py +146 -0
  29. veyra_proof/tools/machete.py +71 -0
  30. veyra_proof/tools/miri.py +86 -0
  31. veyra_proof/tools/mutants.py +82 -0
  32. veyra_proof/tools/nextest.py +68 -0
  33. veyra_proof/tools/reproducible_build.py +185 -0
  34. veyra_proof/tools/rustfmt.py +77 -0
  35. veyra_proof/tools/sast_optional.py +167 -0
  36. veyra_proof/tools/semver_checks.py +90 -0
  37. veyra_proof/tools/vet.py +71 -0
  38. veyra_proof/workspace.py +136 -0
  39. veyra_proof-0.2.0.dist-info/METADATA +339 -0
  40. veyra_proof-0.2.0.dist-info/RECORD +44 -0
  41. veyra_proof-0.2.0.dist-info/WHEEL +5 -0
  42. veyra_proof-0.2.0.dist-info/entry_points.txt +2 -0
  43. veyra_proof-0.2.0.dist-info/licenses/LICENSE +22 -0
  44. veyra_proof-0.2.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,4 @@
1
+ """Veyra Proof — local Rust workspace auditor."""
2
+
3
+ __version__ = "0.2.0"
4
+ __all__ = ["__version__"]
@@ -0,0 +1,6 @@
1
+ """Entry point for ``python -m veyra_proof``."""
2
+
3
+ from veyra_proof.cli import main
4
+
5
+ if __name__ == "__main__":
6
+ raise SystemExit(main())
veyra_proof/cli.py ADDED
@@ -0,0 +1,530 @@
1
+ """CLI for Veyra Proof."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import argparse
6
+ import json
7
+ import sys
8
+ import traceback
9
+ from dataclasses import asdict
10
+ from pathlib import Path
11
+ from typing import Sequence
12
+
13
+ from veyra_proof import __version__
14
+ from veyra_proof.command_runner import CommandRunner
15
+ from veyra_proof.config import load_veyra_config
16
+ from veyra_proof.discovery import run_doctor
17
+ from veyra_proof.hashing import compare_snapshots, snapshot_project_files
18
+ from veyra_proof.installer import confirm_installs, execute_install_plan
19
+ from veyra_proof.models import DetectionResult, InstallPlan, ToolResult
20
+ from veyra_proof.platform_info import detect_platform
21
+ from veyra_proof.reporting import (
22
+ classify_project_changes,
23
+ decide,
24
+ default_evidence_dir,
25
+ render_markdown,
26
+ utc_timestamp_dir,
27
+ write_evidence,
28
+ )
29
+ from veyra_proof.tool_registry import (
30
+ required_tool_ids,
31
+ registry_rows,
32
+ select_tools,
33
+ unknown_include_ids,
34
+ )
35
+ from veyra_proof.tools.base import AuditContext, utc_now_iso
36
+ from veyra_proof.workspace import WorkspaceError, resolve_workspace
37
+
38
+
39
+ def _parse_csv(value: str | None) -> list[str]:
40
+ if not value:
41
+ return []
42
+ return [part.strip() for part in value.split(",") if part.strip()]
43
+
44
+
45
+ def build_parser() -> argparse.ArgumentParser:
46
+ parser = argparse.ArgumentParser(
47
+ prog="veyra",
48
+ description="Audit a local Rust workspace against defective, insecure, or theatrical/fake code.",
49
+ )
50
+ parser.add_argument("--version", action="version", version=f"veyra {__version__}")
51
+
52
+ sub = parser.add_subparsers(dest="command", required=True)
53
+
54
+ sub.add_parser("doctor", help="Diagnose Python/OS/Cargo/rustup without installing anything")
55
+ sub.add_parser("list-tools", help="List tool registry metadata")
56
+
57
+ def add_common(p: argparse.ArgumentParser) -> None:
58
+ p.add_argument("path", type=str, help="Path to Cargo package or workspace")
59
+ p.add_argument("--profile", choices=["quick", "standard", "hardened"], default="standard")
60
+ p.add_argument("--yes", action="store_true", help="Non-interactive authorization for installs")
61
+ p.add_argument("--install-missing", action="store_true", help="Allow installing compatible missing tools")
62
+ p.add_argument("--update-lockfile", action="store_true", help="Authorize cargo update")
63
+ p.add_argument(
64
+ "--allow-manifest-changes",
65
+ action="store_true",
66
+ help="Authorize future Cargo.toml mutations (none implemented in V1)",
67
+ )
68
+ p.add_argument("--include", type=str, default="", help="Comma-separated tool ids to run only")
69
+ p.add_argument("--exclude", type=str, default="", help="Comma-separated tool ids to exclude")
70
+ p.add_argument("--evidence-dir", type=str, default=None)
71
+ p.add_argument("--format", choices=["terminal", "markdown", "json", "both"], default="both")
72
+ p.add_argument("--fail-on", choices=["none", "findings", "error", "inconclusive"], default="findings")
73
+ p.add_argument("--dry-run", action="store_true")
74
+ p.add_argument("--timeout", type=float, default=900.0)
75
+ p.add_argument("--verbose", action="store_true")
76
+
77
+ plan = sub.add_parser("plan", help="Show execution plan without running checks")
78
+ add_common(plan)
79
+
80
+ audit = sub.add_parser("audit", help="Run the audit")
81
+ add_common(audit)
82
+ return parser
83
+
84
+
85
+ def cmd_doctor() -> int:
86
+ report = run_doctor(CommandRunner())
87
+ print(json.dumps(report.to_dict(), indent=2))
88
+ if not report.cargo_ok:
89
+ print("\nCargo is required and was not detected.", file=sys.stderr)
90
+ return 2
91
+ return 0
92
+
93
+
94
+ def cmd_list_tools() -> int:
95
+ print(json.dumps(registry_rows(), indent=2))
96
+ return 0
97
+
98
+
99
+ def _build_plan_dict(
100
+ *,
101
+ ctx: AuditContext,
102
+ tools: list,
103
+ detections: dict[str, DetectionResult],
104
+ install_plans: list[InstallPlan],
105
+ profile: str,
106
+ update_lockfile: bool,
107
+ allow_manifest_changes: bool,
108
+ yes: bool,
109
+ unknown_includes: list[str],
110
+ ) -> dict:
111
+ commands_preview = []
112
+ for tool in tools:
113
+ det = detections.get(tool.id)
114
+ cmds = tool.planned_commands(ctx) if det and det.available else []
115
+ if det and not det.available:
116
+ note = det.message or "missing"
117
+ elif not cmds:
118
+ note = "no commands (likely config-gated skip at run time)"
119
+ else:
120
+ note = "ready"
121
+ commands_preview.append(
122
+ {
123
+ "tool": tool.id,
124
+ "available": bool(det and det.available),
125
+ "commands": cmds,
126
+ "note": note,
127
+ }
128
+ )
129
+ return {
130
+ "profile": profile,
131
+ "selected_tools": [t.id for t in tools],
132
+ "unknown_includes": unknown_includes,
133
+ "detections": {k: v.to_dict() for k, v in detections.items()},
134
+ "pending_installs": [p.to_dict() for p in install_plans],
135
+ "commands_preview": commands_preview,
136
+ "cargo_lock_policy": {
137
+ "update_lockfile_authorized": update_lockfile,
138
+ "will_run_cargo_update": update_lockfile,
139
+ },
140
+ "cargo_toml_policy": {
141
+ "allow_manifest_changes": allow_manifest_changes,
142
+ "automatic_mutations_implemented": False,
143
+ "message": (
144
+ "Manifest changes are authorized but no automatic manifest mutation is implemented."
145
+ if allow_manifest_changes
146
+ else "Cargo.toml modifications are prohibited by default."
147
+ ),
148
+ },
149
+ "authorization_mode": "non-interactive (--yes)" if yes else "interactive",
150
+ "timestamp_utc": ctx.extras.get("timestamp"),
151
+ }
152
+
153
+
154
+ def _prepare_context(
155
+ args: argparse.Namespace,
156
+ *,
157
+ create_evidence_dir: bool,
158
+ ) -> tuple[AuditContext | None, dict, list, dict[str, DetectionResult], list[InstallPlan], int | None]:
159
+ platform = detect_platform(CommandRunner(verbose=bool(args.verbose)))
160
+ if not platform.cargo_path:
161
+ print("ERROR: Cargo not found on PATH. Install Rust/Cargo before auditing.", file=sys.stderr)
162
+ return None, {}, [], {}, [], 2
163
+
164
+ try:
165
+ workspace = resolve_workspace(args.path, CommandRunner(verbose=bool(args.verbose)))
166
+ except WorkspaceError as exc:
167
+ print(f"ERROR: {exc}", file=sys.stderr)
168
+ return None, {}, [], {}, [], 2
169
+
170
+ config = load_veyra_config(Path(workspace.workspace_root))
171
+ include = _parse_csv(args.include)
172
+ exclude = _parse_csv(args.exclude)
173
+ unknown = unknown_include_ids(include)
174
+ if unknown:
175
+ print(f"WARNING: unknown --include tool id(s): {', '.join(unknown)}", file=sys.stderr)
176
+
177
+ tools = select_tools(
178
+ args.profile,
179
+ include=include,
180
+ exclude=exclude,
181
+ config_enable=config.tools.enable,
182
+ config_disable=config.tools.disable,
183
+ )
184
+
185
+ timestamp = utc_timestamp_dir()
186
+ if args.evidence_dir:
187
+ evidence = Path(args.evidence_dir)
188
+ # If user path has no timestamp leaf, still record our stamp for reports
189
+ else:
190
+ evidence = default_evidence_dir(Path(workspace.workspace_root), timestamp)
191
+
192
+ if create_evidence_dir and not args.dry_run:
193
+ evidence.mkdir(parents=True, exist_ok=True)
194
+ (evidence / "tools").mkdir(exist_ok=True)
195
+
196
+ runner = CommandRunner(dry_run=bool(args.dry_run), verbose=bool(args.verbose))
197
+ detect_runner = CommandRunner(verbose=bool(args.verbose), dry_run=False)
198
+
199
+ common = dict(
200
+ workspace=workspace,
201
+ platform=platform,
202
+ config=config,
203
+ evidence_dir=evidence,
204
+ profile=args.profile,
205
+ timeout_seconds=float(args.timeout),
206
+ verbose=bool(args.verbose),
207
+ yes=bool(args.yes),
208
+ install_missing=bool(args.install_missing),
209
+ update_lockfile=bool(args.update_lockfile),
210
+ allow_manifest_changes=bool(args.allow_manifest_changes),
211
+ include=include,
212
+ exclude=exclude,
213
+ extras={"timestamp": timestamp},
214
+ )
215
+
216
+ ctx = AuditContext(runner=runner, dry_run=bool(args.dry_run), **common)
217
+ detect_ctx = AuditContext(runner=detect_runner, dry_run=False, **common)
218
+
219
+ detections: dict[str, DetectionResult] = {}
220
+ install_plans: list[InstallPlan] = []
221
+ for tool in tools:
222
+ if not tool.platform_supported(platform):
223
+ detections[tool.id] = DetectionResult(
224
+ tool.id,
225
+ "SKIPPED",
226
+ False,
227
+ message=f"Incompatible with {platform.os.value}/{platform.architecture}",
228
+ )
229
+ continue
230
+ det = tool.detect(detect_ctx)
231
+ detections[tool.id] = det
232
+ if not det.available and args.install_missing:
233
+ plan = tool.install_plan(detect_ctx)
234
+ if plan is not None:
235
+ install_plans.append(plan)
236
+
237
+ plan = _build_plan_dict(
238
+ ctx=detect_ctx,
239
+ tools=tools,
240
+ detections=detections,
241
+ install_plans=install_plans,
242
+ profile=args.profile,
243
+ update_lockfile=bool(args.update_lockfile),
244
+ allow_manifest_changes=bool(args.allow_manifest_changes),
245
+ yes=bool(args.yes),
246
+ unknown_includes=unknown,
247
+ )
248
+ return ctx, plan, tools, detections, install_plans, None
249
+
250
+
251
+ def cmd_plan(args: argparse.Namespace) -> int:
252
+ ctx, plan, tools, detections, install_plans, err = _prepare_context(args, create_evidence_dir=False)
253
+ if ctx is None:
254
+ return err or 2
255
+ for tool in tools:
256
+ det = detections.get(tool.id)
257
+ if det and not det.available and tool.id not in {p.tool_id for p in install_plans}:
258
+ plan.setdefault("auto_skip", []).append({"tool": tool.id, "reason": det.message or "missing"})
259
+ print(json.dumps(plan, indent=2))
260
+ return 0
261
+
262
+
263
+ def cmd_audit(args: argparse.Namespace) -> int:
264
+ ctx, plan, tools, detections, install_plans, err = _prepare_context(args, create_evidence_dir=True)
265
+ if ctx is None:
266
+ return err or 2
267
+
268
+ timestamp = ctx.extras["timestamp"]
269
+
270
+ if args.allow_manifest_changes:
271
+ print(
272
+ "Manifest changes are authorized but no automatic manifest mutation is implemented.",
273
+ file=sys.stderr,
274
+ )
275
+
276
+ if args.dry_run:
277
+ print(json.dumps({"dry_run": True, "plan": plan}, indent=2))
278
+ return 0
279
+
280
+ lockfile_existed = ctx.workspace.has_lockfile
281
+ before = snapshot_project_files(ctx.root)
282
+ hashes_before = {k: v.to_dict() for k, v in before.items()}
283
+
284
+ live = CommandRunner(verbose=bool(args.verbose), dry_run=False)
285
+
286
+ if install_plans:
287
+ authorized = confirm_installs(install_plans, yes=bool(args.yes))
288
+ if not authorized:
289
+ print("Installation not authorized. Aborting before mutating the system.", file=sys.stderr)
290
+ return 2
291
+ plan["install_authorization"] = "non-interactive (--yes)" if args.yes else "interactive confirmation"
292
+ for ip in install_plans:
293
+ outcome = execute_install_plan(ip, live, timeout=float(args.timeout))
294
+ plan.setdefault("install_outcomes", []).append(asdict(outcome))
295
+ tool = next(t for t in tools if t.id == ip.tool_id)
296
+ if not outcome.success:
297
+ # Persist install failure as ERROR result
298
+ out, errp = tool.write_logs(
299
+ ctx,
300
+ json.dumps(outcome.steps_run, indent=2),
301
+ outcome.error or "install failed",
302
+ )
303
+ fail = tool.make_result(
304
+ status="ERROR",
305
+ severity="error",
306
+ summary=outcome.error or "installation failed",
307
+ started_at=utc_now_iso(),
308
+ command=ip.steps[-1].command if ip.steps else None,
309
+ exit_code=1,
310
+ stdout_path=out,
311
+ stderr_path=errp,
312
+ environment={"phase": "install"},
313
+ )
314
+ tool.save_result_json(ctx, fail)
315
+ detections[tool.id] = DetectionResult(
316
+ tool.id, "ERROR", False, message=outcome.error or "install failed"
317
+ )
318
+ # Keep going with other tools; this tool will be ERROR/skipped below
319
+ else:
320
+ detections[tool.id] = tool.detect(AuditContext(
321
+ workspace=ctx.workspace,
322
+ platform=ctx.platform,
323
+ config=ctx.config,
324
+ runner=live,
325
+ evidence_dir=ctx.evidence_dir,
326
+ profile=ctx.profile,
327
+ timeout_seconds=ctx.timeout_seconds,
328
+ verbose=ctx.verbose,
329
+ include=ctx.include,
330
+ exclude=ctx.exclude,
331
+ extras=ctx.extras,
332
+ ))
333
+
334
+ if args.update_lockfile:
335
+ if not args.yes:
336
+ try:
337
+ answer = input("Authorize `cargo update`? [y/N]: ")
338
+ except EOFError:
339
+ answer = "n"
340
+ if answer.strip().lower() not in {"y", "yes"}:
341
+ print("cargo update not authorized.", file=sys.stderr)
342
+ return 2
343
+ upd = live.run(["cargo", "update"], cwd=ctx.root, timeout=args.timeout)
344
+ plan["cargo_update"] = upd.to_dict()
345
+ if upd.timed_out or upd.exit_code != 0:
346
+ print("cargo update failed; continuing with existing lockfile state.", file=sys.stderr)
347
+ else:
348
+ try:
349
+ ctx.workspace = resolve_workspace(ctx.workspace.workspace_root, live)
350
+ except WorkspaceError as exc:
351
+ print(f"ERROR after cargo update: {exc}", file=sys.stderr)
352
+ return 2
353
+
354
+ results: list[ToolResult] = []
355
+ selected_ids = [t.id for t in tools]
356
+ req_ids = required_tool_ids(
357
+ args.profile,
358
+ workspace=ctx.workspace,
359
+ selected_ids=selected_ids,
360
+ config=ctx.config,
361
+ )
362
+ missing_required: list[str] = []
363
+
364
+ # Ensure evidence runner is live
365
+ ctx.runner = live
366
+
367
+ for tool in tools:
368
+ det = detections.get(tool.id)
369
+ if det and det.status == "ERROR" and not det.available:
370
+ # install failed already recorded
371
+ existing = ctx.evidence_dir / "tools" / tool.id / "result.json"
372
+ if existing.is_file():
373
+ data = json.loads(existing.read_text(encoding="utf-8"))
374
+ results.append(
375
+ ToolResult(
376
+ tool_id=data["tool_id"],
377
+ display_name=data["display_name"],
378
+ status=data["status"],
379
+ severity=data["severity"],
380
+ command=data.get("command"),
381
+ exit_code=data.get("exit_code"),
382
+ started_at=data["started_at"],
383
+ finished_at=data["finished_at"],
384
+ duration_seconds=data["duration_seconds"],
385
+ stdout_path=data.get("stdout_path"),
386
+ stderr_path=data.get("stderr_path"),
387
+ findings=[],
388
+ summary=data["summary"],
389
+ environment=data.get("environment") or {},
390
+ artifact_hashes=data.get("artifact_hashes") or {},
391
+ )
392
+ )
393
+ continue
394
+ if det and det.status == "SKIPPED":
395
+ results.append(
396
+ tool.skipped(ctx, det.message or "skipped", skip_kind="incompatible")
397
+ )
398
+ continue
399
+ if det and not det.available:
400
+ kind = "missing"
401
+ results.append(tool.skipped(ctx, det.message or "tool missing and not installed", skip_kind=kind))
402
+ if tool.id in req_ids:
403
+ missing_required.append(tool.id)
404
+ continue
405
+ try:
406
+ result = tool.run(ctx)
407
+ if not (ctx.evidence_dir / "tools" / tool.id / "result.json").is_file():
408
+ tool.save_result_json(ctx, result)
409
+ except Exception as exc: # noqa: BLE001
410
+ out, errp = tool.write_logs(ctx, "", traceback.format_exc())
411
+ result = tool.make_result(
412
+ status="ERROR",
413
+ severity="error",
414
+ summary=f"Internal tool error: {exc}",
415
+ started_at=utc_now_iso(),
416
+ stdout_path=out,
417
+ stderr_path=errp,
418
+ )
419
+ tool.save_result_json(ctx, result)
420
+ results.append(result)
421
+
422
+ after = snapshot_project_files(ctx.root)
423
+ raw_changes = compare_snapshots(before, after)
424
+ changes, unauthorized = classify_project_changes(
425
+ raw_changes,
426
+ update_lockfile=bool(args.update_lockfile),
427
+ allow_manifest_changes=bool(args.allow_manifest_changes),
428
+ lockfile_existed_before=lockfile_existed,
429
+ )
430
+ hashes_after = {k: v.to_dict() for k, v in after.items()}
431
+
432
+ decision = decide(
433
+ results,
434
+ fail_on=args.fail_on,
435
+ required_tool_ids=req_ids,
436
+ hash_incongruence=bool(unauthorized),
437
+ missing_required=missing_required,
438
+ )
439
+ if unauthorized:
440
+ decision.reason = (
441
+ f"Unauthorized project file changes detected: {', '.join(unauthorized)}. " + decision.reason
442
+ )
443
+ decision.decision = "REJECTED"
444
+ decision.exit_code = 1
445
+
446
+ os_arch = f"{ctx.platform.os.value} / {ctx.platform.architecture}"
447
+ toolchain = ctx.platform.active_toolchain or ctx.platform.rustc_version or "unknown"
448
+ md = render_markdown(
449
+ workspace=ctx.workspace.workspace_root,
450
+ timestamp=timestamp,
451
+ os_arch=os_arch,
452
+ toolchain=toolchain,
453
+ profile=args.profile,
454
+ decision=decision,
455
+ results=results,
456
+ changes=changes,
457
+ )
458
+ report_json = {
459
+ "workspace": ctx.workspace.to_dict(),
460
+ "decision": decision.to_dict(),
461
+ "results": [r.to_dict() for r in results],
462
+ "changes": changes,
463
+ "required_tool_ids": req_ids,
464
+ "config_defaults_used": ctx.config.defaults_used,
465
+ "install_authorization": plan.get("install_authorization"),
466
+ "timestamp_utc": timestamp,
467
+ }
468
+ manifest = {
469
+ "version": __version__,
470
+ "timestamp_utc": timestamp,
471
+ "profile": args.profile,
472
+ "fail_on": args.fail_on,
473
+ "decision": decision.to_dict(),
474
+ "platform": ctx.platform.to_dict(),
475
+ "commands": [r.command for r in results],
476
+ "results_status": {r.tool_id: r.status for r in results},
477
+ "hashes_before": hashes_before,
478
+ "hashes_after": hashes_after,
479
+ "required_tool_ids": req_ids,
480
+ }
481
+ write_evidence(
482
+ ctx.evidence_dir,
483
+ report_md=md,
484
+ report_json=report_json,
485
+ manifest=manifest,
486
+ environment=ctx.platform.to_dict(),
487
+ workspace=ctx.workspace.to_dict(),
488
+ changes=changes,
489
+ plan=plan,
490
+ hashes={"before": hashes_before, "after": hashes_after},
491
+ )
492
+
493
+ fmt = args.format
494
+ if fmt in {"terminal", "both", "markdown"}:
495
+ print(md)
496
+ print(f"\nEvidence written to: {ctx.evidence_dir}")
497
+ if fmt in {"json"}:
498
+ print(json.dumps(report_json, indent=2))
499
+ if fmt == "both":
500
+ print(f"JSON report: {ctx.evidence_dir / 'report.json'}")
501
+
502
+ return decision.exit_code
503
+
504
+
505
+ def main(argv: Sequence[str] | None = None) -> int:
506
+ parser = build_parser()
507
+ try:
508
+ args = parser.parse_args(list(argv) if argv is not None else None)
509
+ if args.command == "doctor":
510
+ return cmd_doctor()
511
+ if args.command == "list-tools":
512
+ return cmd_list_tools()
513
+ if args.command == "plan":
514
+ return cmd_plan(args)
515
+ if args.command == "audit":
516
+ return cmd_audit(args)
517
+ parser.error(f"Unknown command: {args.command}")
518
+ return 3
519
+ except SystemExit as exc:
520
+ code = exc.code
521
+ return int(code) if isinstance(code, int) else (0 if code is None else 3)
522
+ except Exception as exc: # noqa: BLE001
523
+ print(f"INTERNAL ERROR: {exc}", file=sys.stderr)
524
+ if "--verbose" in (argv or sys.argv):
525
+ traceback.print_exc()
526
+ return 3
527
+
528
+
529
+ if __name__ == "__main__":
530
+ raise SystemExit(main())