verify-industry-details-release.py 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. """Verify a frozen detail-evidence candidate, including a disposable publisher exercise."""
  2. import argparse
  3. import ast
  4. import hashlib
  5. import json
  6. from pathlib import Path
  7. import sqlite3
  8. import subprocess
  9. import tempfile
  10. ROOT = Path(__file__).resolve().parents[1]
  11. def sha(data):
  12. return hashlib.sha256(data).hexdigest()
  13. def main():
  14. p = argparse.ArgumentParser(description=__doc__)
  15. p.add_argument('--package', required=True, type=Path)
  16. p.add_argument('--out', required=True, type=Path)
  17. args = p.parse_args()
  18. package = args.package.resolve()
  19. release = json.loads((package / 'RELEASE.json').read_bytes())
  20. assert release['overallComplete'] is False and release['productionDeployed'] is False
  21. assert release['privateDetailOriginalsIncluded'] is False
  22. assert not list(package.rglob('detail-originals'))
  23. manifest = json.loads((package / 'deploy/industry-ui-files.json').read_bytes())
  24. assert len(manifest['files']) == 10
  25. for entry in manifest['files']:
  26. assert sha((package / 'payload' / entry['path']).read_bytes()) == entry['sha256']
  27. syntax_count = 0
  28. for path in package.rglob('*.py'):
  29. ast.parse(path.read_text('utf-8-sig'), feature_version=(3, 9))
  30. syntax_count += 1
  31. assets = list((package / 'data/monitoring-registry/assets').iterdir())
  32. for path in assets:
  33. assert sha(path.read_bytes()) == path.stem
  34. with sqlite3.connect(package / 'data/monitoring-registry/monitoring.sqlite') as c:
  35. assert c.execute('PRAGMA integrity_check').fetchone()[0] == 'ok'
  36. detail_rows = c.execute('SELECT product_id,projection_sha FROM detail_evidence').fetchall()
  37. assert len(detail_rows) == 4
  38. for product, digest in detail_rows:
  39. projection = json.loads((package / 'data/monitoring-registry/assets' / (digest + '.json')).read_bytes())
  40. assert projection['productId'] == product
  41. assert set(projection).issubset({'schema', 'generation', 'productId', 'title', 'platformFamily', 'marketplace',
  42. 'marketplaceProof', 'returnedShopId', 'shopIdentityPolicy', 'requestId', 'endpointId', 'observedAtRaw',
  43. 'timezoneKnown', 'rawSha256', 'proofPointers', 'fields', 'state', 'rawVerifiedAtImport', 'orderBasis', 'policy'})
  44. baseline = ROOT / 'work/industry-monitoring-20261107-r1/release-baseline/index.html'
  45. assert sha(baseline.read_bytes()) == manifest['homepageSha256']
  46. program = r'''
  47. import fs from 'node:fs';
  48. import path from 'node:path';
  49. import assert from 'node:assert/strict';
  50. import {pathToFileURL} from 'node:url';
  51. const [candidate,base,temporary,index]=process.argv.slice(1);
  52. const {publishFiles}=await import(pathToFileURL(path.join(candidate,'scripts/publish-industry-ui.mjs')));
  53. const target=path.join(temporary,'site');
  54. fs.cpSync(path.join(base,'payload'),target,{recursive:true});
  55. fs.copyFileSync(index,path.join(target,'index.html'));
  56. const options={sourceDir:path.join(candidate,'payload'),manifestPath:path.join(candidate,'deploy/industry-ui-files.json'),targetDir:target};
  57. const dry=publishFiles(options);assert.equal(dry.readOnly,true);assert.equal(dry.changes.length,3);
  58. const applied=publishFiles({...options,apply:true});assert.equal(applied.filesystemVerified,true);
  59. const repeated=publishFiles({...options,apply:true});assert.equal(repeated.changes.length,0);
  60. fs.appendFileSync(path.join(target,'src/pages/industry-detail-view.js'),'\n// concurrent change');
  61. let rejected=false;try{publishFiles({...options,apply:true});}catch(e){rejected=e.code==='UNKNOWN_TARGET_CONTENT';}
  62. assert.equal(rejected,true);
  63. console.log(JSON.stringify({ok:true,dryChanges:dry.changes.length,applyVerified:true,idempotent:true,unknownConcurrentFileRejected:true,networkRequests:0}));
  64. '''
  65. # Keep this temporary test fixture for audit; never point it at a live site.
  66. temporary = Path(tempfile.mkdtemp(prefix='r7-publisher-', dir=ROOT / 'work/industry-monitoring-20261107-r1'))
  67. completed = subprocess.run(['node', '--input-type=module', '-e', program, str(package),
  68. str(ROOT / 'artifacts/ksf-industry-monitor-20261107-r6/package'), str(temporary), str(baseline)],
  69. check=True, capture_output=True, text=True, encoding='utf-8')
  70. publisher = json.loads(completed.stdout)
  71. result = {'ok': True, 'uiFilesVerified': 10, 'python39SyntaxFiles': syntax_count,
  72. 'nativePython39Verified': False, 'auxiliaryIntegrity': 'ok', 'assetsVerified': len(assets),
  73. 'detailObservations': 4, 'privateDetailOriginalsIncluded': False,
  74. 'publisher': publisher, 'paidCalls': 0, 'productionWrites': 0, 'productionDeployed': False}
  75. args.out.write_text(json.dumps(result, ensure_ascii=False, indent=2) + '\n', 'utf-8')
  76. print(json.dumps(result, ensure_ascii=False))
  77. if __name__ == '__main__': main()