#!/usr/bin/env python3
"""Fail-closed static validator for the Base V3 partner package (no network or chain actions)."""
from __future__ import annotations
import json, re, subprocess, sys
sys.dont_write_bytecode = True
from pathlib import Path
from strict_json_schema import StrictJSONError, load_json as strict_load, validate
ROOT = Path(__file__).resolve().parents[1]
ADDRESS = re.compile(r'^0x[0-9a-fA-F]{40}$')
CLASSES = {'signer-guardian','attesters','adjudicators','design-partner','module-route','caps','factory-permission','write-activation'}
PHRASES = {'APPROVAL: decoded calldata matches this single action and this manifest version.','APPROVAL: I verified the target, value, cap/maximum-loss impact, and V2 preservation impact.','APPROVAL: I confirm the 24-hour timelock and independent-review evidence are complete for this exact action.'}
ORGANIZATIONAL_DOCS = {
    'PARTNER_READINESS_RELEASE_CHECKLIST.md',
    'docs/ORGANIZATIONAL_COMMITMENTS.md',
    'docs/FLOURISHING_BONDS_PRODUCT_STATEMENT.md',
    'docs/EVIDENCE_ATTESTATION_SPEC.md',
    'docs/TWO_PHASE_CANDIDATE_FREEZE.md',
    'docs/PILOT_COMMERCIAL_TERMS_AND_SCOPE.md',
    'docs/NAMED_ROLE_COLLECTION_TEMPLATE.md',
    'docs/SECURITY_AND_VULNERABILITY_REPORTING.md',
    'docs/INCIDENT_RESPONSE_AND_ESCALATION.md',
    'docs/SUPPORT_SLA_MAINTENANCE_DEPRECATION.md',
    'docs/DEPLOYMENT_CHANGE_MANAGEMENT_RACI.md',
    'docs/COMMERCIAL_LEGAL_ENTITY_SIGNATORY_CHECKLIST.md',
    'docs/V3_OPERATIONS_RECONCILIATION.md',
    'schemas/organizational-readiness.schema.json',
    'schemas/trusted-identities.schema.json',
    'schemas/trust-root-policy.schema.json',
    'schemas/legal-registry-trust-policy.schema.json',
    'schemas/privacy-execution-trust-policy.schema.json',
    'schemas/pilot-scope-registry.schema.json',
    'schemas/pilot-rollout-plan.schema.json',
    'schemas/evidence/artifact-manifest.schema.json',
    'schemas/evidence/bounded-liabilities.schema.json',
    'schemas/evidence/change-raci-acceptance.schema.json',
    'schemas/evidence/commercial-agreement.schema.json',
    'schemas/evidence/contact-control.schema.json',
    'schemas/evidence/incident-plan-acceptance.schema.json',
    'schemas/evidence/incident-exercise.schema.json',
    'schemas/evidence/legal-entity-verification.schema.json',
    'schemas/evidence/legal-entity-registry-response.schema.json',
    'schemas/evidence/legal-entity-raw-registry-response.schema.json',
    'schemas/evidence/contact-reachability-test.schema.json',
    'schemas/evidence/contact-provider-receipt.schema.json',
    'schemas/evidence/privacy-portability.schema.json',
    'schemas/evidence/security-policy-acceptance.schema.json',
    'schemas/evidence/signature-set.schema.json',
    'schemas/evidence/support-sla-acceptance.schema.json',
    'schemas/evidence/test-results.schema.json',
    'schemas/evidence/unlinkability-test-results.schema.json',
    'schemas/evidence/privacy-unlinkability-log.schema.json',
    'schemas/evidence/privacy-unlinkability-transcript.schema.json',
    'schemas/evidence/privacy-unlinkability-methodology-input.schema.json',
    'schemas/evidence/privacy-unlinkability-seed-commitment.schema.json',
    'schemas/evidence/privacy-unlinkability-execution-output.schema.json',
    'schemas/evidence/incident-objective-log.schema.json',
    'schemas/evidence/incident-objective-output.schema.json',
    'schemas/evidence/incident-assertion-evidence.schema.json',
    'manifests/organizational-readiness.template.json',
    'config/trusted-identities.json',
    'config/trust-root-policy.json',
    'config/legal-registry-trust-policy.json',
    'config/privacy-execution-trust-policy.json',
    'config/pilot-scope-registry.json',
    'config/pilot-rollout-plan.json',
    'scripts/strict_json_schema.py',
    'scripts/run_unlinkability_protocol.py',
}
def load(path: Path):
    try: return strict_load(path)
    except StrictJSONError as e: raise ValueError(f'{path.relative_to(ROOT)} invalid JSON: {e}')
def require(ok: bool, message: str):
    if not ok: raise ValueError(message)
def reject_generated_files():
    forbidden = []
    for path in ROOT.rglob('*'):
        if not path.is_file():
            continue
        relative = path.relative_to(ROOT)
        if (
            '__pycache__' in relative.parts
            or path.suffix in {'.pyc', '.pyo'}
            or path.name in {'.DS_Store', '.coverage'}
            or path.name.endswith(('.tmp', '.swp', '~'))
        ):
            forbidden.append(relative.as_posix())
    require(not forbidden, f'unexpected cache/generated files: {sorted(forbidden)}')
def run():
    reject_generated_files()
    execution_policy = load(ROOT/'config/privacy-execution-trust-policy.json')
    validate(
        execution_policy,
        load(ROOT/'schemas/privacy-execution-trust-policy.schema.json'),
    )
    require(
        execution_policy['status'] == 'disabled-no-genuine-execution-authority'
        and execution_policy['positiveReadinessAllowed'] is False,
        'privacy execution must remain structurally blocked absent genuine authority',
    )
    manifest = load(ROOT/'manifests/base-v3-deployment-manifest.template.json')
    validate(manifest, load(ROOT/'schemas/deployment-manifest.schema.json'))
    require(manifest['schema']=='vaultfire.base-v3-deployment-manifest.v1','manifest schema drift')
    require(manifest['chain']=={'name':'base','chainId':8453},'manifest chain must be Base 8453')
    require(manifest['status']=='pilot-prepared' and manifest['pilotReady'] is False,'manifest must remain pilot-prepared with pilotReady false')
    require(manifest['v2Default'] is True,'V2 must remain default')
    truth=manifest.get('pilotTruthfulness',{})
    require(truth == {
        'baseOnly':True,
        'activeDesignPartnerLimit':1,
        'requiredActiveDesignPartnersForActivation':1,
        'currentActiveDesignPartners':0,
        'privacyMeaning':'data minimization and commitments; Base transactions remain public',
        'disputeFilingBond':'fully refundable filing bond; never a fee, slash, or forfeiture',
        'flourishingMeasurements':'purpose-only; never controls principal, collateral, or any protected decision',
        'excludedValueFlows':['solana','cross-chain'],
    },'manifest pilot truthfulness constraints drift')
    evidence=manifest.get('evidencePlaceholders',{})
    require(evidence == {
        'writtenPartnerAcceptance':None,
        'qualifiedCounselReview':None,
        'baseSepoliaRehearsal':None,
        'x402SettlementEvidence':None,
        'mainnetApproval':None,
    },'manifest invents pilot evidence or approval')
    require(manifest['roles']['signers']==[None]*5,'manifest invents signer roles')
    require(all(not manifest['roles'][k] for k in ('guardians','attesters','adjudicators','designPartners')),'manifest invents a role or partner')
    require(not manifest['contracts'] and not manifest['routes'] and not manifest['caps'],'manifest invents deployment, route, or cap data')
    require(all(v is None for v in manifest['releaseEvidence'].values()),'manifest invents release evidence')
    proposals = sorted((ROOT/'proposals').glob('*.json'))
    require(len(proposals)==8,'exactly eight proposal templates are required')
    seen=set()
    for path in proposals:
        item=load(path); gov=item.get('governance',{}); action=item.get('action',{})
        validate(item, load(ROOT/'schemas/proposal-template.schema.json'))
        require(item.get('schema')=='vaultfire.base-v3-governance-proposal.v1',f'{path.name}: schema drift')
        require(item.get('status')=='template-not-submittable',f'{path.name}: must remain non-submittable')
        require(item.get('actionClass') in CLASSES and item['actionClass'] not in seen,f'{path.name}: missing/duplicate action class (bundling)')
        seen.add(item['actionClass'])
        require(gov.get('signerSlots')==[None]*5 and gov.get('threshold')==3 and gov.get('timelockSeconds')==86400,f'{path.name}: not blank 3-of-5 24h')
        require(gov.get('implementationReference') is None,f'{path.name}: invents governance implementation')
        require(action.get('target') is None and action.get('calldata') is None and action.get('valueWei')=='0' and action.get('parameters')=={},f'{path.name}: executable or populated action')
        require(item.get('approvalEvidence')==[],f'{path.name}: contains invented approval evidence')
        require(set(item.get('approvalRequirements',[]))==PHRASES,f'{path.name}: required approval phrases missing')
        require(item.get('blockedOn'),f'{path.name}: missing blockers')
    require(seen==CLASSES,'missing required proposal class')
    missing = sorted(path for path in ORGANIZATIONAL_DOCS if not (ROOT/path).is_file())
    require(not missing, f'missing organizational package files: {missing}')
    org = subprocess.run(
        [sys.executable, '-B', str(ROOT/'scripts/check_organizational_readiness.py')],
        cwd=ROOT, text=True, capture_output=True, check=False,
    )
    require(org.returncode == 0, f'organizational readiness validation failed: {org.stderr.strip()}')
    org_report = json.loads(org.stdout)
    require(org_report['partnerReady'] is False and org_report['status'] == 'blocked',
            'checked-in organizational manifest must remain truthfully blocked')
    print(json.dumps({'verified':'Base V3 partner package','proposalTemplateCount':len(proposals),'proposalClasses':sorted(seen),'pilotReady':False,'organizationalPartnerReady':False,'organizationalStatus':'blocked','v2Default':True,'activeDesignPartners':0,'networkActions':'none'},indent=2))
if __name__=='__main__':
    try: run()
    except (KeyError,ValueError,StrictJSONError) as exc: print(f'PARTNER PACKAGE BLOCKED: {exc}',file=sys.stderr); raise SystemExit(1)
