#!/usr/bin/env python3 """Audit release qualification; never change acceptance, build or publish anything.""" import json import re from pathlib import Path, PurePosixPath from native_report import analyze, clean from source_manifest import addon_manifest from native_provenance import qualify_archive, MANIFEST, METADATA, PROVENANCE ROOT = Path(__file__).resolve().parents[1] REQUIRED_CASES = ( 'independent_native_build', 'prison_geometry_and_spawn', 'escape_loop', 'join_late', 'reconnect_identity', 'death_unconsciousness_revive', 'respawn_and_spectators', 'partial_escape', 'defeat_restart', 'interrupted_reports', 'stale_contacts', 'support_losses', 'ammunition_depletion', 'cleanup_no_duplicate_orders_or_spawns', 'twelve_players', 'equivalent_workload_fps_improvement', 'own_identity_release_package', ) STATUSES = {'pending', 'passed', 'failed'} def strict_object(pairs): result = {} for key, value in pairs: if key in result: raise ValueError(f'duplicate JSON key: {key}') result[key] = value return result def read_json(path): def invalid_constant(value): raise ValueError(f'invalid JSON number: {value}') return json.loads(path.read_text(encoding='utf-8'), object_pairs_hook=strict_object, parse_constant=invalid_constant) def evidence_path(root, name): if not isinstance(name, str) or not name or '\\' in name or any(ord(c) < 32 for c in name): raise ValueError('evidence path must be a nonempty repository-relative POSIX path') path = PurePosixPath(name) if (path.is_absolute() or path.as_posix() != name or ':' in name or any(part in {'.', '..', '.git'} for part in path.parts)): raise ValueError(f'unsafe or noncanonical evidence path: {name}') full = root / name try: resolved = full.resolve() except (OSError, RuntimeError) as error: raise ValueError(f'evidence path cannot be resolved: {name}') from error if not resolved.is_relative_to(root): raise ValueError(f'evidence leaves repository: {name}') cursor = root for part in path.parts: cursor /= part if cursor.is_symlink(): raise ValueError(f'evidence symlinks are not supported: {name}') if not full.is_file(): raise ValueError(f'evidence is not an existing regular file: {name}') with full.open('rb') as stream: if not any(chunk.strip() for chunk in iter(lambda: stream.read(65536), b'')): raise ValueError(f'evidence is empty: {name}') return full def audit(root=ROOT): root = Path(root).resolve() blocked = [] try: ledger = read_json(evidence_path(root, 'docs/acceptance.json')) except (OSError, ValueError) as error: return [f'docs/acceptance.json: {error}'] if not isinstance(ledger, dict): return ['acceptance ledger must be an object'] if not isinstance(ledger.get('target'), str) or not ledger['target'].strip(): blocked.append('target must be a nonempty string') cases = ledger.get('cases') if not isinstance(cases, dict): blocked.append('cases must be an object containing the fixed first-release requirements') cases = {} for name in REQUIRED_CASES: if name not in cases: blocked.append(f'cases.{name}: required first-release case is missing') for name in sorted(set(cases) - set(REQUIRED_CASES)): blocked.append(f'cases.{name}: unknown first-release case') development = ledger.get('development_checks') if not isinstance(development, dict): blocked.append('development_checks must be an object') development = {} if 'clean_native_runtime' not in development: blocked.append('development_checks.clean_native_runtime: required clean-runtime gate is missing') def validate_entry(label, entry, required): if not isinstance(entry, dict): blocked.append(f'{label}: entry must be an object') return status = entry.get('status') if not isinstance(status, str) or status not in STATUSES: blocked.append(f'{label}: unknown or malformed status {status!r}') elif required and status != 'passed': blocked.append(f'{label}: status is {status}, not passed') evidence = entry.get('evidence') if not isinstance(evidence, list): blocked.append(f'{label}: evidence must be a list of file paths') return if status == 'passed' and not evidence: blocked.append(f'{label}: passed status requires nonempty evidence') seen = set() for name in evidence: try: evidence_path(root, name) if name in seen: raise ValueError(f'duplicate evidence path: {name}') seen.add(name) except (OSError, ValueError) as error: blocked.append(f'{label}: {error}') for name, entry in cases.items(): validate_entry(f'cases.{name}', entry, True) for name, entry in development.items(): validate_entry(f'development_checks.{name}', entry, name == 'clean_native_runtime') current = None try: current = addon_manifest(root) except (OSError, ValueError) as error: blocked.append(f'current addon source/resource manifest: {error}') def native_qualification(label, entry): if not isinstance(entry, dict) or entry.get('status') != 'passed': return paths = {} for field in ('source_manifest', 'native_report'): try: paths[field] = evidence_path(root, entry.get(field)) if not isinstance(entry.get('evidence'), list) or entry[field] not in entry['evidence']: raise ValueError(f'{field} must also appear in this entry\'s evidence list') except (OSError, ValueError) as error: blocked.append(f'{label}.{field}: {error}') if len(paths) != 2: return if paths['source_manifest'].parent != paths['native_report'].parent: blocked.append(f'{label}: native report and manifest must belong to the same evidence archive') try: manifest = read_json(paths['source_manifest']) if (not isinstance(manifest, dict) or not manifest or any(not isinstance(digest, str) or not re.fullmatch(r'[0-9a-f]{64}', digest) for digest in manifest.values())): raise ValueError('manifest must be a nonempty path-to-SHA256 object') if current is not None and manifest != current: missing = sorted(set(current) - set(manifest)) extra = sorted(set(manifest) - set(current)) changed = sorted(path for path in set(current) & set(manifest) if current[path] != manifest[path]) raise ValueError(f'manifest does not match current addon sources/resources ' f'(missing={len(missing)}, extra={len(extra)}, changed={len(changed)})') except (OSError, ValueError) as error: blocked.append(f'{label}.source_manifest: {error}') required_rounds = 1 try: archive = paths['native_report'].parent for name in (MANIFEST, METADATA, PROVENANCE, 'runner.json.txt', 'source-sha256.json'): evidence_path(root, (archive / name).relative_to(root).as_posix()) required_rounds = qualify_archive(archive, current) except (OSError, ValueError) as error: blocked.append(f'{label}.launch_provenance: {error}') try: report = read_json(paths['native_report']) if not isinstance(report, dict): raise ValueError('native report must be an object') log = evidence_path(root, report.get('log')) if log.parent != paths['native_report'].parent: raise ValueError('native raw log must belong to the same evidence archive') if log.name != 'console.log.txt': raise ValueError('native report must analyze the exact console.log.txt sealed by the stopped runner') actual = analyze(log.read_text(errors='replace'), report['log']) # Exact JSON types matter: True must not masquerade as one completion. if json.dumps(report, sort_keys=True) != json.dumps(actual, sort_keys=True): raise ValueError('native report does not match analysis of its archived raw log') if not clean(actual, required_rounds) or actual['assertions_passed'] <= 0: raise ValueError('native raw log must contain successful completion/assertions and no script, engine or end-screen errors') except (OSError, ValueError) as error: blocked.append(f'{label}.native_report: {error}') native_qualification('cases.independent_native_build', cases.get('independent_native_build')) native_qualification('development_checks.clean_native_runtime', development.get('clean_native_runtime')) return blocked def main(): blocked = audit() if blocked: print('Not release-qualified:') for reason in blocked: print(' - ' + reason) return 1 print('Release qualification audit passed. This audit does not build or publish a release.') return 0 if __name__ == '__main__': raise SystemExit(main())