verify-industry-policy-release.py 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. """Verify unified r9 contents and exercise deployment only against a disposable r8 site."""
  2. import argparse
  3. import ast
  4. from contextlib import closing
  5. import hashlib
  6. import json
  7. from pathlib import Path
  8. import sqlite3
  9. import subprocess
  10. import tempfile
  11. ROOT = Path(__file__).resolve().parents[1]
  12. def sha(raw):
  13. return hashlib.sha256(raw).hexdigest()
  14. def main():
  15. parser = argparse.ArgumentParser(description=__doc__)
  16. parser.add_argument('--package', type=Path, required=True)
  17. parser.add_argument('--out', type=Path, required=True)
  18. args = parser.parse_args()
  19. package = args.package.resolve()
  20. release = json.loads((package / 'RELEASE.json').read_bytes())
  21. assert release['release'] == 'ksf-industry-monitor-20261107-r9'
  22. assert not release['overallComplete'] and not release['productionDeployed']
  23. assert release['innovationWorkspaceImplemented'] and release['requestGuardImplemented']
  24. assert release['existingVocCoreModulesReplaced'] == 0
  25. assert not release['credentialsIncluded'] and not release['privateDetailOriginalsIncluded']
  26. assert not list(package.rglob('detail-originals'))
  27. assert not list(package.rglob('snapshot.json'))
  28. manifest = json.loads((package / 'deploy/industry-ui-files.json').read_bytes())
  29. assert len(manifest['files']) == 14 and len(release['backendFiles']) == 15
  30. assert set(e['path'] for e in manifest['files']) == set(release['staticFiles'])
  31. for entry in manifest['files']:
  32. assert sha((package / 'payload' / entry['path']).read_bytes().replace(b'\r\n', b'\n')) == entry['sha256']
  33. for name in release['backendFiles']:
  34. assert (package / name).is_file(), name
  35. syntax = list(package.rglob('*.py'))
  36. for path in syntax:
  37. ast.parse(path.read_text('utf-8-sig'), feature_version=(3, 9))
  38. assets = list((package / 'data/monitoring-registry/assets').iterdir())
  39. for path in assets:
  40. assert sha(path.read_bytes()) == path.stem, path
  41. database = package / 'data/monitoring-registry/monitoring.sqlite'
  42. with closing(sqlite3.connect(database.resolve().as_uri() + '?mode=ro', uri=True)) as connection:
  43. assert connection.execute('PRAGMA integrity_check').fetchone()[0] == 'ok'
  44. assert connection.execute('SELECT count(*) FROM detail_evidence').fetchone()[0] == 4
  45. assert connection.execute('SELECT count(*) FROM detail_endpoint_proofs').fetchone()[0] == 1
  46. assert connection.execute('SELECT count(DISTINCT document_id) FROM policy_source_checks').fetchone()[0] == 25
  47. assert connection.execute("SELECT count(*) FROM pipeline_workers WHERE state<>'ARCHIVED_COPY'").fetchone()[0] == 0
  48. scope = json.loads((package / 'handoff/collector-scope.json').read_bytes())
  49. assert not scope['dispatchAllowed'] and scope['budget']['remainingDispatchMinor'] == 0
  50. assert scope['primaryShop']['products'] == 165 and len(scope['shops']) == 8
  51. assert scope['budget']['identifiedSettledRequests'] == 76
  52. assert scope['budget']['aggregateOnlyRows'] == 78
  53. baseline = ROOT / 'work/industry-monitoring-20261107-r1/release-baseline/index.html'
  54. assert sha(baseline.read_bytes()) == manifest['homepageSha256']
  55. program = r'''
  56. import fs from 'node:fs';
  57. import path from 'node:path';
  58. import assert from 'node:assert/strict';
  59. import crypto from 'node:crypto';
  60. import {pathToFileURL} from 'node:url';
  61. const [candidate,base,temporary,index]=process.argv.slice(1);
  62. const {publishFiles}=await import(pathToFileURL(path.join(candidate,'scripts/publish-industry-ui.mjs')));
  63. const target=path.join(temporary,'site');
  64. fs.cpSync(path.join(base,'payload'),target,{recursive:true});
  65. fs.copyFileSync(index,path.join(target,'index.html'));
  66. const manifest=JSON.parse(fs.readFileSync(path.join(candidate,'deploy/industry-ui-files.json')));
  67. const normalized=p=>crypto.createHash('sha256').update(fs.readFileSync(p,'utf8').replaceAll('\r\n','\n')).digest('hex');
  68. const expected=manifest.files.filter(e=>!fs.existsSync(path.join(target,e.path)) || normalized(path.join(target,e.path))!==e.sha256).length;
  69. const options={sourceDir:path.join(candidate,'payload'),manifestPath:path.join(candidate,'deploy/industry-ui-files.json'),targetDir:target};
  70. const dry=publishFiles(options);assert.equal(dry.readOnly,true);assert.equal(dry.changes.length,expected);
  71. const applied=publishFiles({...options,apply:true});assert.equal(applied.filesystemVerified,true);
  72. const repeated=publishFiles({...options,apply:true});assert.equal(repeated.changes.length,0);
  73. fs.appendFileSync(path.join(target,'src/pages/industry-detail-view.js'),'\n// concurrent change');
  74. let rejected=false;try{publishFiles({...options,apply:true});}catch(e){rejected=e.code==='UNKNOWN_TARGET_CONTENT';}
  75. assert.equal(rejected,true);
  76. console.log(JSON.stringify({ok:true,dryChanges:dry.changes.length,applyVerified:true,idempotent:true,unknownConcurrentFileRejected:true,networkRequests:0}));
  77. '''
  78. temporary = Path(tempfile.mkdtemp(prefix='r9-publisher-', dir=ROOT / 'work/industry-monitoring-20261107-r1'))
  79. completed = subprocess.run(['node', '--input-type=module', '-e', program, str(package),
  80. str(ROOT / 'artifacts/ksf-industry-monitor-20261107-r8/package'), str(temporary), str(baseline)],
  81. capture_output=True, text=True, encoding='utf-8')
  82. if completed.returncode:
  83. raise AssertionError(completed.stderr)
  84. native = json.loads((package/'evidence/r9-native39/verification.json').read_bytes())
  85. assert native['ok'] and native['runtime']['nativePython39Verified']
  86. assert native['unitTests'] == {'testsRun':92, 'failures':0, 'errors':0, 'ok':True}
  87. assert native['generation'] == scope['generation']
  88. assert native['productionVerified'] is False
  89. result = {'ok': True, 'staticFiles': 14, 'backendFiles': 15, 'python39SyntaxFiles': len(syntax),
  90. 'nativePython39Verified': True, 'nativePython39Platform': native['runtime']['platform'],
  91. 'productionNativeRuntimeVerified': False, 'assetsVerified': len(assets), 'auxiliaryIntegrity': 'ok',
  92. 'detailObservations': 4, 'appendedEndpointProofs': 1, 'officialPolicyMetadataVerified': 9, 'privateOriginalsIncluded': False,
  93. 'publisher': json.loads(completed.stdout), 'overallComplete': False,
  94. 'productionDeployed': False, 'productionWrites': 0, 'paidCalls': 0}
  95. args.out.parent.mkdir(parents=True, exist_ok=True)
  96. args.out.write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n', 'utf-8')
  97. print(json.dumps(result, ensure_ascii=False))
  98. if __name__ == '__main__':
  99. main()