gea-program 0.42.0__tar.gz → 0.43.0__tar.gz

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 (331) hide show
  1. {gea_program-0.42.0 → gea_program-0.43.0}/PKG-INFO +14 -1
  2. {gea_program-0.42.0 → gea_program-0.43.0}/README.md +13 -0
  3. {gea_program-0.42.0 → gea_program-0.43.0}/gea/__init__.py +1 -1
  4. {gea_program-0.42.0 → gea_program-0.43.0}/gea/__main__.py +80 -0
  5. {gea_program-0.42.0 → gea_program-0.43.0}/gea/acceptance_tests.py +207 -0
  6. {gea_program-0.42.0 → gea_program-0.43.0}/gea/client_reports.py +5 -0
  7. gea_program-0.43.0/gea/dossier.py +369 -0
  8. gea_program-0.43.0/gea/dossier_verify.py +136 -0
  9. gea_program-0.43.0/gea/ed25519.py +131 -0
  10. {gea_program-0.42.0 → gea_program-0.43.0}/gea/files.py +3 -1
  11. gea_program-0.43.0/gea/help/dossier.md +15 -0
  12. gea_program-0.43.0/gea/help/report-history.md +11 -0
  13. {gea_program-0.42.0 → gea_program-0.43.0}/gea/helplib.py +2 -0
  14. gea_program-0.43.0/gea/report_history.py +148 -0
  15. {gea_program-0.42.0 → gea_program-0.43.0}/gea/service.py +37 -0
  16. {gea_program-0.42.0 → gea_program-0.43.0}/gea/web/app.html +32 -1
  17. {gea_program-0.42.0 → gea_program-0.43.0}/gea_program.egg-info/PKG-INFO +14 -1
  18. {gea_program-0.42.0 → gea_program-0.43.0}/gea_program.egg-info/SOURCES.txt +6 -0
  19. {gea_program-0.42.0 → gea_program-0.43.0}/pyproject.toml +1 -1
  20. {gea_program-0.42.0 → gea_program-0.43.0}/LICENSE +0 -0
  21. {gea_program-0.42.0 → gea_program-0.43.0}/gea/BENCH_TEST_PROTOCOL.md +0 -0
  22. {gea_program-0.42.0 → gea_program-0.43.0}/gea/accuracy_statement.py +0 -0
  23. {gea_program-0.42.0 → gea_program-0.43.0}/gea/alarm_engine.py +0 -0
  24. {gea_program-0.42.0 → gea_program-0.43.0}/gea/backup.py +0 -0
  25. {gea_program-0.42.0 → gea_program-0.43.0}/gea/bench.py +0 -0
  26. {gea_program-0.42.0 → gea_program-0.43.0}/gea/blind_harness.py +0 -0
  27. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/acex_lomonosov_age_depth_model.provenance.json +0 -0
  28. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/acex_lomonosov_age_depth_model.txt +0 -0
  29. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/agassiz77_canada_temperature.csv +0 -0
  30. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/agassiz77_canada_temperature.provenance.json +0 -0
  31. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/barbados_110_consolidation.provenance.json +0 -0
  32. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/barbados_110_consolidation.txt +0 -0
  33. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/bengal_u1452_grain_size.provenance.json +0 -0
  34. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/bengal_u1452_grain_size.txt +0 -0
  35. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/blake_164_methane_isotopes.provenance.json +0 -0
  36. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/blake_164_methane_isotopes.txt +0 -0
  37. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.provenance.json +0 -0
  38. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/chicxulub_m0077a_pwave_velocity.txt +0 -0
  39. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/collingwood_1_28_ks_complete.las +0 -0
  40. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/collingwood_1_28_ks_complete.provenance.json +0 -0
  41. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/costa_rica_odp_friction_envelope.provenance.json +0 -0
  42. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/costa_rica_odp_friction_envelope.txt +0 -0
  43. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.provenance.json +0 -0
  44. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/dead_sea_5017_debrite_xrf_ms.txt +0 -0
  45. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/dsdp_504b_physical_properties.provenance.json +0 -0
  46. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/dsdp_504b_physical_properties.txt +0 -0
  47. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/dsdp_504b_sound_velocity.provenance.json +0 -0
  48. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/dsdp_504b_sound_velocity.txt +0 -0
  49. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/elgygytgyn_5011_turbidites.provenance.json +0 -0
  50. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/elgygytgyn_5011_turbidites.txt +0 -0
  51. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/epica_domec_co2_800kyr.provenance.json +0 -0
  52. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/epica_domec_co2_800kyr.txt +0 -0
  53. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/fram_909_organic_petrography.provenance.json +0 -0
  54. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/fram_909_organic_petrography.txt +0 -0
  55. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/gbr_325_coral_uth_ages.provenance.json +0 -0
  56. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/gbr_325_coral_uth_ages.txt +0 -0
  57. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/gisp2_greenland_temperature.csv +0 -0
  58. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/gisp2_greenland_temperature.provenance.json +0 -0
  59. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/gom_308_t2p_insitu.provenance.json +0 -0
  60. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/gom_308_t2p_insitu.txt +0 -0
  61. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/guaymas_385_dom_d13c.provenance.json +0 -0
  62. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/guaymas_385_dom_d13c.txt +0 -0
  63. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/hikurangi_u1520_friction_insitu.provenance.json +0 -0
  64. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/hikurangi_u1520_friction_insitu.txt +0 -0
  65. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.provenance.json +0 -0
  66. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/hydrate_ridge_204_ncr_hydrate.txt +0 -0
  67. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/iodp_u1324_pore_pressure.provenance.json +0 -0
  68. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/iodp_u1324_pore_pressure.txt +0 -0
  69. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/jfast_c0019_slow_slip_events.provenance.json +0 -0
  70. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/jfast_c0019_slow_slip_events.txt +0 -0
  71. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/kennetcook_2_p129_excerpt.las +0 -0
  72. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/kennetcook_2_p129_excerpt.provenance.json +0 -0
  73. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_bhgm_density.dat +0 -0
  74. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_bhgm_density.provenance.json +0 -0
  75. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_complog_6020_excerpt.provenance.json +0 -0
  76. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_complog_6020_excerpt.txt +0 -0
  77. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_hlog246_temperature.dat +0 -0
  78. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_hlog246_temperature.provenance.json +0 -0
  79. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_rockmech_compress.dat +0 -0
  80. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_rockmech_compress.provenance.json +0 -0
  81. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.dat +0 -0
  82. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_hb_tvd_0_9080_excerpt.provenance.json +0 -0
  83. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_vb_rockmech_compress.dat +0 -0
  84. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_vb_rockmech_compress.provenance.json +0 -0
  85. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_vb_vlog251_temperature.dat +0 -0
  86. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ktb_vb_vlog251_temperature.provenance.json +0 -0
  87. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/l06_06_nl_survey.csv +0 -0
  88. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/l06_06_nl_survey.provenance.json +0 -0
  89. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/l07_01_nl_excerpt.las +0 -0
  90. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/l07_01_nl_excerpt.provenance.json +0 -0
  91. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/mariana_1200_serpentinite_geochem.provenance.json +0 -0
  92. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/mariana_1200_serpentinite_geochem.txt +0 -0
  93. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/med_160_sapropels.provenance.json +0 -0
  94. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/med_160_sapropels.txt +0 -0
  95. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/nankai_megasplay_shear_strength.provenance.json +0 -0
  96. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/nankai_megasplay_shear_strength.txt +0 -0
  97. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_1027b_thermal_conductivity.provenance.json +0 -0
  98. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_1027b_thermal_conductivity.txt +0 -0
  99. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_1027c_cork_temperature.provenance.json +0 -0
  100. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_1027c_cork_temperature.txt +0 -0
  101. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_1165b_thermal_conductivity.provenance.json +0 -0
  102. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_1165b_thermal_conductivity.txt +0 -0
  103. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.provenance.json +0 -0
  104. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_1274a_mantle_peridotite_mad.txt +0 -0
  105. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_504b_dike_elastic_moduli.provenance.json +0 -0
  106. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_504b_dike_elastic_moduli.txt +0 -0
  107. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_504b_leg137_borehole_fluids.provenance.json +0 -0
  108. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_504b_leg137_borehole_fluids.txt +0 -0
  109. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.provenance.json +0 -0
  110. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/odp_735b_gabbro_elastic_moduli.txt +0 -0
  111. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/peru_201_sulfate_reduction.provenance.json +0 -0
  112. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/peru_201_sulfate_reduction.txt +0 -0
  113. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/scorpio_e1_sa_excerpt.las +0 -0
  114. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/scorpio_e1_sa_excerpt.provenance.json +0 -0
  115. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/sumatra_362_cohesion.provenance.json +0 -0
  116. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/sumatra_362_cohesion.txt +0 -0
  117. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/university_6_17_no1_tx_excerpt.las +0 -0
  118. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/university_6_17_no1_tx_excerpt.provenance.json +0 -0
  119. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ursa_308_xrd_mineralogy.provenance.json +0 -0
  120. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/ursa_308_xrd_mineralogy.txt +0 -0
  121. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/volve_15_9_19_sr_excerpt.las +0 -0
  122. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/volve_15_9_19_sr_excerpt.provenance.json +0 -0
  123. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/volve_15_9_19a_core_excerpt.csv +0 -0
  124. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/volve_15_9_19a_core_excerpt.provenance.json +0 -0
  125. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/volve_f12_f14_production_excerpt.csv +0 -0
  126. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/volve_f12_f14_production_excerpt.provenance.json +0 -0
  127. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/walvis_208_petm_carbonate.provenance.json +0 -0
  128. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/walvis_208_petm_carbonate.txt +0 -0
  129. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/woodlark_1109_rock_eval.provenance.json +0 -0
  130. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog/woodlark_1109_rock_eval.txt +0 -0
  131. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog_map.py +0 -0
  132. {gea_program-0.42.0 → gea_program-0.43.0}/gea/catalog_register.py +0 -0
  133. {gea_program-0.42.0 → gea_program-0.43.0}/gea/certificates.py +0 -0
  134. {gea_program-0.42.0 → gea_program-0.43.0}/gea/cli.py +0 -0
  135. {gea_program-0.42.0 → gea_program-0.43.0}/gea/config_versioning.py +0 -0
  136. {gea_program-0.42.0 → gea_program-0.43.0}/gea/conformance.py +0 -0
  137. {gea_program-0.42.0 → gea_program-0.43.0}/gea/control_room.py +0 -0
  138. {gea_program-0.42.0 → gea_program-0.43.0}/gea/correlation.py +0 -0
  139. {gea_program-0.42.0 → gea_program-0.43.0}/gea/dashboard.py +0 -0
  140. {gea_program-0.42.0 → gea_program-0.43.0}/gea/declarations.py +0 -0
  141. {gea_program-0.42.0 → gea_program-0.43.0}/gea/deviation.py +0 -0
  142. {gea_program-0.42.0 → gea_program-0.43.0}/gea/dlis.py +0 -0
  143. {gea_program-0.42.0 → gea_program-0.43.0}/gea/dnp3.py +0 -0
  144. {gea_program-0.42.0 → gea_program-0.43.0}/gea/doctor.py +0 -0
  145. {gea_program-0.42.0 → gea_program-0.43.0}/gea/downhole_engine.py +0 -0
  146. {gea_program-0.42.0 → gea_program-0.43.0}/gea/drift_monitor.py +0 -0
  147. {gea_program-0.42.0 → gea_program-0.43.0}/gea/drill_string.py +0 -0
  148. {gea_program-0.42.0 → gea_program-0.43.0}/gea/earth_model.py +0 -0
  149. {gea_program-0.42.0 → gea_program-0.43.0}/gea/economics.py +0 -0
  150. {gea_program-0.42.0 → gea_program-0.43.0}/gea/edr.py +0 -0
  151. {gea_program-0.42.0 → gea_program-0.43.0}/gea/elevation.py +0 -0
  152. {gea_program-0.42.0 → gea_program-0.43.0}/gea/etp.py +0 -0
  153. {gea_program-0.42.0 → gea_program-0.43.0}/gea/etp12_schemas.json +0 -0
  154. {gea_program-0.42.0 → gea_program-0.43.0}/gea/example_register_map.json +0 -0
  155. {gea_program-0.42.0 → gea_program-0.43.0}/gea/fat_sat.py +0 -0
  156. {gea_program-0.42.0 → gea_program-0.43.0}/gea/fixtures/README.md +0 -0
  157. {gea_program-0.42.0 → gea_program-0.43.0}/gea/fixtures/comcat_delaware_basin_2026-09.txt +0 -0
  158. {gea_program-0.42.0 → gea_program-0.43.0}/gea/fixtures/comcat_mahoning_1990-2026.txt +0 -0
  159. {gea_program-0.42.0 → gea_program-0.43.0}/gea/fixtures/comcat_youngstown_2011-2012.txt +0 -0
  160. {gea_program-0.42.0 → gea_program-0.43.0}/gea/fixtures/ne_110m_coastline.json +0 -0
  161. {gea_program-0.42.0 → gea_program-0.43.0}/gea/follower.py +0 -0
  162. {gea_program-0.42.0 → gea_program-0.43.0}/gea/forward_model.py +0 -0
  163. {gea_program-0.42.0 → gea_program-0.43.0}/gea/friction_priors.py +0 -0
  164. {gea_program-0.42.0 → gea_program-0.43.0}/gea/gamma.py +0 -0
  165. {gea_program-0.42.0 → gea_program-0.43.0}/gea/gauge_aging.py +0 -0
  166. {gea_program-0.42.0 → gea_program-0.43.0}/gea/gauge_specs.py +0 -0
  167. {gea_program-0.42.0 → gea_program-0.43.0}/gea/geodesy.py +0 -0
  168. {gea_program-0.42.0 → gea_program-0.43.0}/gea/gravity_reference.py +0 -0
  169. {gea_program-0.42.0 → gea_program-0.43.0}/gea/hartip.py +0 -0
  170. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/TESTER_GUIDE.md +0 -0
  171. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/alarms.md +0 -0
  172. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/audit-update.md +0 -0
  173. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/backup.md +0 -0
  174. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/catalog.md +0 -0
  175. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/charts.md +0 -0
  176. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/conformance.md +0 -0
  177. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/control-room.md +0 -0
  178. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/data-in.md +0 -0
  179. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/declare.md +0 -0
  180. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/dlis.md +0 -0
  181. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/doctor.md +0 -0
  182. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/drift.md +0 -0
  183. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/drillstring.md +0 -0
  184. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/economics.md +0 -0
  185. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/edr.md +0 -0
  186. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/etp.md +0 -0
  187. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/field-ports.md +0 -0
  188. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/files.md +0 -0
  189. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/historian.md +0 -0
  190. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/instruments.md +0 -0
  191. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/interval.md +0 -0
  192. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/map.md +0 -0
  193. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/month-end.md +0 -0
  194. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/notifications.md +0 -0
  195. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/opseis.md +0 -0
  196. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/osdu.md +0 -0
  197. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/patches.md +0 -0
  198. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/penetrator.md +0 -0
  199. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/ppdm.md +0 -0
  200. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/quakeml.md +0 -0
  201. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/quality.md +0 -0
  202. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/seedlink.md +0 -0
  203. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/seismic.md +0 -0
  204. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/setup.md +0 -0
  205. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/simulation.md +0 -0
  206. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/site.md +0 -0
  207. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/sites.md +0 -0
  208. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/slip.md +0 -0
  209. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/soak.md +0 -0
  210. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/sra.md +0 -0
  211. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/start.md +0 -0
  212. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/structure.md +0 -0
  213. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/tools.md +0 -0
  214. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/transients.md +0 -0
  215. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/upkeep.md +0 -0
  216. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/vibration.md +0 -0
  217. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/well-report.md +0 -0
  218. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/well-tests.md +0 -0
  219. {gea_program-0.42.0 → gea_program-0.43.0}/gea/help/well3d.md +0 -0
  220. {gea_program-0.42.0 → gea_program-0.43.0}/gea/housekeeping.py +0 -0
  221. {gea_program-0.42.0 → gea_program-0.43.0}/gea/interval.py +0 -0
  222. {gea_program-0.42.0 → gea_program-0.43.0}/gea/inverse_engine.py +0 -0
  223. {gea_program-0.42.0 → gea_program-0.43.0}/gea/jobs.py +0 -0
  224. {gea_program-0.42.0 → gea_program-0.43.0}/gea/live_ports.py +0 -0
  225. {gea_program-0.42.0 → gea_program-0.43.0}/gea/loadtest.py +0 -0
  226. {gea_program-0.42.0 → gea_program-0.43.0}/gea/matplotlib_demo.py +0 -0
  227. {gea_program-0.42.0 → gea_program-0.43.0}/gea/modbus.py +0 -0
  228. {gea_program-0.42.0 → gea_program-0.43.0}/gea/modbus_rtu.py +0 -0
  229. {gea_program-0.42.0 → gea_program-0.43.0}/gea/model_card.py +0 -0
  230. {gea_program-0.42.0 → gea_program-0.43.0}/gea/month_end.py +0 -0
  231. {gea_program-0.42.0 → gea_program-0.43.0}/gea/mqtt_port.py +0 -0
  232. {gea_program-0.42.0 → gea_program-0.43.0}/gea/notify.py +0 -0
  233. {gea_program-0.42.0 → gea_program-0.43.0}/gea/op_seismicity.py +0 -0
  234. {gea_program-0.42.0 → gea_program-0.43.0}/gea/opcua_port.py +0 -0
  235. {gea_program-0.42.0 → gea_program-0.43.0}/gea/operator_app.py +0 -0
  236. {gea_program-0.42.0 → gea_program-0.43.0}/gea/osdu.py +0 -0
  237. {gea_program-0.42.0 → gea_program-0.43.0}/gea/patches.py +0 -0
  238. {gea_program-0.42.0 → gea_program-0.43.0}/gea/penetrator.py +0 -0
  239. {gea_program-0.42.0 → gea_program-0.43.0}/gea/permits.py +0 -0
  240. {gea_program-0.42.0 → gea_program-0.43.0}/gea/piwebapi.py +0 -0
  241. {gea_program-0.42.0 → gea_program-0.43.0}/gea/ports.py +0 -0
  242. {gea_program-0.42.0 → gea_program-0.43.0}/gea/ppdm.py +0 -0
  243. {gea_program-0.42.0 → gea_program-0.43.0}/gea/profile_catalog.py +0 -0
  244. {gea_program-0.42.0 → gea_program-0.43.0}/gea/project.py +0 -0
  245. {gea_program-0.42.0 → gea_program-0.43.0}/gea/qt6_downhole_app.py +0 -0
  246. {gea_program-0.42.0 → gea_program-0.43.0}/gea/quakeml.py +0 -0
  247. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reconciler.py +0 -0
  248. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/IU_ANMO_10_BHZ_response.provenance.json +0 -0
  249. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/IU_ANMO_10_BHZ_response.xml +0 -0
  250. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/eia_henry_hub_daily.csv.gz +0 -0
  251. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/eia_prices.provenance.json +0 -0
  252. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/eia_wti_daily.csv.gz +0 -0
  253. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/libmseed_steim.provenance.json +0 -0
  254. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/libmseed_steim1_4096.mseed +0 -0
  255. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/libmseed_steim2_4096.mseed +0 -0
  256. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/libmseed_steim2_512.mseed +0 -0
  257. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/mahoning_example.json +0 -0
  258. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/mud_motor_specs.json +0 -0
  259. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/penetrator_ratings.json +0 -0
  260. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/seismicity_protocols.json +0 -0
  261. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/tool_sources.json +0 -0
  262. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/us_outline.json +0 -0
  263. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/us_outline.provenance.json +0 -0
  264. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_3d.provenance.json +0 -0
  265. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_f4_interpretation.las.gz +0 -0
  266. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_f4_logs.provenance.json +0 -0
  267. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_f4_lwd.las.gz +0 -0
  268. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_surfaces.json.gz +0 -0
  269. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_traj_F-10.xml.gz +0 -0
  270. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_traj_F-4.xml.gz +0 -0
  271. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_traj_F-5_12.25in.xml.gz +0 -0
  272. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_traj_F-5_17.5in.xml.gz +0 -0
  273. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_traj_F-5_36in.xml.gz +0 -0
  274. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_traj_F-5_8.5in.xml.gz +0 -0
  275. {gea_program-0.42.0 → gea_program-0.43.0}/gea/reference/volve_well_picks.csv.gz +0 -0
  276. {gea_program-0.42.0 → gea_program-0.43.0}/gea/rock_inventory.py +0 -0
  277. {gea_program-0.42.0 → gea_program-0.43.0}/gea/sample_record.py +0 -0
  278. {gea_program-0.42.0 → gea_program-0.43.0}/gea/sample_well_profile.csv +0 -0
  279. {gea_program-0.42.0 → gea_program-0.43.0}/gea/sbom.py +0 -0
  280. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seedlink.py +0 -0
  281. {gea_program-0.42.0 → gea_program-0.43.0}/gea/segy.py +0 -0
  282. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic.py +0 -0
  283. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_array.py +0 -0
  284. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_assoc.py +0 -0
  285. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_detect.py +0 -0
  286. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_film.py +0 -0
  287. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_harmonic.py +0 -0
  288. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_qc.py +0 -0
  289. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_response.py +0 -0
  290. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_signature.py +0 -0
  291. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_track.py +0 -0
  292. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_uncertainty.py +0 -0
  293. {gea_program-0.42.0 → gea_program-0.43.0}/gea/seismic_unlisted.py +0 -0
  294. {gea_program-0.42.0 → gea_program-0.43.0}/gea/sensor_swap.py +0 -0
  295. {gea_program-0.42.0 → gea_program-0.43.0}/gea/series_cache.py +0 -0
  296. {gea_program-0.42.0 → gea_program-0.43.0}/gea/service_life.py +0 -0
  297. {gea_program-0.42.0 → gea_program-0.43.0}/gea/setup_checklist.py +0 -0
  298. {gea_program-0.42.0 → gea_program-0.43.0}/gea/shapefile.py +0 -0
  299. {gea_program-0.42.0 → gea_program-0.43.0}/gea/shell.py +0 -0
  300. {gea_program-0.42.0 → gea_program-0.43.0}/gea/shut_in.py +0 -0
  301. {gea_program-0.42.0 → gea_program-0.43.0}/gea/simulation.py +0 -0
  302. {gea_program-0.42.0 → gea_program-0.43.0}/gea/site_map.py +0 -0
  303. {gea_program-0.42.0 → gea_program-0.43.0}/gea/sla_report.py +0 -0
  304. {gea_program-0.42.0 → gea_program-0.43.0}/gea/slip.py +0 -0
  305. {gea_program-0.42.0 → gea_program-0.43.0}/gea/soak.py +0 -0
  306. {gea_program-0.42.0 → gea_program-0.43.0}/gea/sra.py +0 -0
  307. {gea_program-0.42.0 → gea_program-0.43.0}/gea/store_forward.py +0 -0
  308. {gea_program-0.42.0 → gea_program-0.43.0}/gea/strata_join.py +0 -0
  309. {gea_program-0.42.0 → gea_program-0.43.0}/gea/structure.py +0 -0
  310. {gea_program-0.42.0 → gea_program-0.43.0}/gea/supervisor.py +0 -0
  311. {gea_program-0.42.0 → gea_program-0.43.0}/gea/survey_cmd.py +0 -0
  312. {gea_program-0.42.0 → gea_program-0.43.0}/gea/survey_view.py +0 -0
  313. {gea_program-0.42.0 → gea_program-0.43.0}/gea/telemetry.py +0 -0
  314. {gea_program-0.42.0 → gea_program-0.43.0}/gea/tool_catalog.py +0 -0
  315. {gea_program-0.42.0 → gea_program-0.43.0}/gea/tool_library.py +0 -0
  316. {gea_program-0.42.0 → gea_program-0.43.0}/gea/tool_taxonomy.py +0 -0
  317. {gea_program-0.42.0 → gea_program-0.43.0}/gea/transient.py +0 -0
  318. {gea_program-0.42.0 → gea_program-0.43.0}/gea/vibration.py +0 -0
  319. {gea_program-0.42.0 → gea_program-0.43.0}/gea/well3d.py +0 -0
  320. {gea_program-0.42.0 → gea_program-0.43.0}/gea/well_assembler.py +0 -0
  321. {gea_program-0.42.0 → gea_program-0.43.0}/gea/well_report.py +0 -0
  322. {gea_program-0.42.0 → gea_program-0.43.0}/gea/well_test_validation.py +0 -0
  323. {gea_program-0.42.0 → gea_program-0.43.0}/gea/wits0.py +0 -0
  324. {gea_program-0.42.0 → gea_program-0.43.0}/gea/witsml.py +0 -0
  325. {gea_program-0.42.0 → gea_program-0.43.0}/gea/workspace.py +0 -0
  326. {gea_program-0.42.0 → gea_program-0.43.0}/gea_program.egg-info/dependency_links.txt +0 -0
  327. {gea_program-0.42.0 → gea_program-0.43.0}/gea_program.egg-info/entry_points.txt +0 -0
  328. {gea_program-0.42.0 → gea_program-0.43.0}/gea_program.egg-info/requires.txt +0 -0
  329. {gea_program-0.42.0 → gea_program-0.43.0}/gea_program.egg-info/top_level.txt +0 -0
  330. {gea_program-0.42.0 → gea_program-0.43.0}/setup.cfg +0 -0
  331. {gea_program-0.42.0 → gea_program-0.43.0}/tests/test_acceptance.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: gea-program
3
- Version: 0.42.0
3
+ Version: 0.43.0
4
4
  Summary: GEA-Program: downhole gauge monitoring - canonical measurement records, quality rules, drift evaluation with a re-fit change log, well-test validation, alarm management, model cards, store-and-forward, monthly SLA measurement, FAT/SAT protocols and a dashboard, generated from live objects in the client's reporting outline.
5
5
  Author: Daniel T. Murphy
6
6
  License: MPL-2.0
@@ -648,6 +648,19 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
648
648
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
649
649
  `rtserve.iris.washington.edu:18000`, network `TX`.
650
650
 
651
+ ### Report history and the signed dossier
652
+
653
+ Every client report a workspace writes is kept, version by version, under
654
+ `reports\history\` with each file's SHA-256, so pressing a report button
655
+ again no longer loses the last one; the Reports tab lists every version.
656
+ The signed dossier puts a well's - or the pad's - reports, declarations,
657
+ audit log and source-record hashes in one zip, signed with the site's own
658
+ Ed25519 key (made on the site's machine, never sent anywhere), with a
659
+ checker inside that anyone can run with plain Python: VALID when the
660
+ signature and every file's hash hold, and inside the workspace a check that
661
+ the source records and the audit log still match what was signed.
662
+ `gea dossier`, `gea reports-history`, `gea help dossier`.
663
+
651
664
  ### Operations and seismicity
652
665
 
653
666
  The Operations & Seismicity tab sets each well's drilling, completion and
@@ -609,6 +609,19 @@ against it; `gea seedlink --config ... --hello` prints what a server says of
609
609
  itself; `gea help seedlink`. A TexNet station is served by EarthScope at
610
610
  `rtserve.iris.washington.edu:18000`, network `TX`.
611
611
 
612
+ ### Report history and the signed dossier
613
+
614
+ Every client report a workspace writes is kept, version by version, under
615
+ `reports\history\` with each file's SHA-256, so pressing a report button
616
+ again no longer loses the last one; the Reports tab lists every version.
617
+ The signed dossier puts a well's - or the pad's - reports, declarations,
618
+ audit log and source-record hashes in one zip, signed with the site's own
619
+ Ed25519 key (made on the site's machine, never sent anywhere), with a
620
+ checker inside that anyone can run with plain Python: VALID when the
621
+ signature and every file's hash hold, and inside the workspace a check that
622
+ the source records and the audit log still match what was signed.
623
+ `gea dossier`, `gea reports-history`, `gea help dossier`.
624
+
612
625
  ### Operations and seismicity
613
626
 
614
627
  The Operations & Seismicity tab sets each well's drilling, completion and
@@ -123,7 +123,7 @@ from .profile_catalog import (
123
123
  read_pangaea_txt,
124
124
  )
125
125
 
126
- __version__ = "0.42.0"
126
+ __version__ = "0.43.0"
127
127
  __all__ = [
128
128
  "aging_rate", "rate_psi_yr", "accuracy_psi",
129
129
  "Sensor", "SimulatorConfig", "DownholeEngine",
@@ -566,6 +566,25 @@ def main(argv=None) -> int:
566
566
  p_os.add_argument("--actor", type=str, default="cli")
567
567
  p_os.add_argument("--json", action="store_true")
568
568
 
569
+ p_dz = sub.add_parser("dossier", help="the signed dossier: a well's (or the pad's) reports, declarations, audit log and source hashes in one zip, signed with the site's Ed25519 key, with a checker anyone can run without this program")
570
+ p_dz.add_argument("--workspace", type=str, default=None)
571
+ p_dz.add_argument("--well", type=str, default=None, help="a well; without it, the pad (every well and the site reports)")
572
+ p_dz.add_argument("--include-sources", action="store_true", help="embed the source records (by default they are listed by hash)")
573
+ p_dz.add_argument("--verify", type=str, default=None, help="a dossier to check (with --workspace, also against this site)")
574
+ p_dz.add_argument("--sources", type=str, default="", help="with --verify: a folder holding the source records to check against their hashes")
575
+ p_dz.add_argument("--key", action="store_true", help="print this site's signing key fingerprint")
576
+ p_dz.add_argument("--list", action="store_true", help="list the dossiers this site has signed")
577
+ p_dz.add_argument("--actor", type=str, default="cli")
578
+ p_dz.add_argument("--json", action="store_true")
579
+
580
+ p_rh = sub.add_parser("reports-history", help="every version of every client report this workspace has written, kept under reports/history; --prune keeps the newest N")
581
+ p_rh.add_argument("--workspace", type=str, required=True)
582
+ p_rh.add_argument("--family", type=str, default=None, help="e.g. wells/Well-1/well_report or pad_report")
583
+ p_rh.add_argument("--prune", action="store_true")
584
+ p_rh.add_argument("--keep", type=int, default=0)
585
+ p_rh.add_argument("--apply", action="store_true", help="with --prune: remove (without it, list what would go)")
586
+ p_rh.add_argument("--json", action="store_true")
587
+
569
588
  p_sm = sub.add_parser("simulation", help="the simulation in words: the scenes (Volve 15/9-F-4 with its logs and interpretation, Volve F-5 and F-10 on the survey clock, every drilling-recorder well), and what the record says at a depth (the Simulation tab replays it)")
570
589
  p_sm.add_argument("--workspace", type=str, default=None)
571
590
  p_sm.add_argument("--scene", type=str, default=None, help="e.g. volve-f4, or well:Well-30636033")
@@ -2226,6 +2245,67 @@ def main(argv=None) -> int:
2226
2245
  return 0
2227
2246
  print(_json.dumps(r, default=str) if a.json else _OS.text(r))
2228
2247
  return 0
2248
+ elif a.cmd == "dossier":
2249
+ import json as _json
2250
+ from . import dossier as _DZ
2251
+ from .cli import _safe_stdout
2252
+ from .workspace import Workspace, WorkspaceError
2253
+ _safe_stdout()
2254
+ ws = None
2255
+ if a.workspace:
2256
+ try:
2257
+ ws = Workspace(a.workspace)
2258
+ except WorkspaceError as e:
2259
+ raise SystemExit(f"dossier: {e}")
2260
+ if a.verify:
2261
+ r = _DZ.verify(a.verify, ws, a.sources)
2262
+ print(_json.dumps(r, default=str) if a.json else _DZ.text(r))
2263
+ return 0 if r["status"] == "VALID" else 1
2264
+ if ws is None:
2265
+ raise SystemExit("dossier: give --workspace (or --verify FILE)")
2266
+ if a.key:
2267
+ k = _DZ.key_info(ws)
2268
+ print(f"Ed25519 {k['fingerprint']} (public key {k['public_key']}, made {k['created_utc']})" if k else "no signing key yet: the first dossier makes one")
2269
+ return 0
2270
+ if a.list:
2271
+ for e in reversed(_DZ.ledger(ws)):
2272
+ print(f"{e['utc']} {e['dossier_id']} {e['display']:<24} {e['n_files']:>4} files {e['path']}")
2273
+ return 0
2274
+ try:
2275
+ e = _DZ.build(ws, a.actor, __version__, a.well, a.include_sources)
2276
+ except (KeyError, ValueError) as ex:
2277
+ raise SystemExit(f"dossier: {ex.args[0] if ex.args else ex}")
2278
+ print(f"signed {e['dossier_id']}: {e['display']}, {e['n_files']} file(s), {e['n_sources']} source record(s) {'embedded' if e['sources_embedded'] else 'by hash'}")
2279
+ print(f" {os.path.join(ws.reports_dir, e['path'])}")
2280
+ print(f" key fingerprint {e['fingerprint']}")
2281
+ return 0
2282
+ elif a.cmd == "reports-history":
2283
+ import json as _json
2284
+ from . import report_history as _RH
2285
+ from .cli import _safe_stdout
2286
+ from .workspace import Workspace, WorkspaceError
2287
+ _safe_stdout()
2288
+ try:
2289
+ ws = Workspace(a.workspace)
2290
+ except WorkspaceError as e:
2291
+ raise SystemExit(f"reports-history: {e}")
2292
+ if a.prune:
2293
+ if a.keep < 1:
2294
+ raise SystemExit("reports-history: --prune needs --keep N (at least 1)")
2295
+ r = _RH.prune(ws.reports_dir, a.keep, a.family, a.apply)
2296
+ print(f"{'removed' if r['applied'] else 'would remove'} {len(r['removed'])} version(s), {r['bytes_freed']:,} bytes" + ("" if r['applied'] else " - add --apply to remove them"))
2297
+ return 0
2298
+ sm = _RH.summary(ws.reports_dir)
2299
+ if a.json:
2300
+ print(_json.dumps(sm, default=str)); return 0
2301
+ print(f"{sm['n_versions']} version(s) of {len(sm['families'])} report(s), {sm['bytes']:,} bytes under {os.path.join(ws.reports_dir, 'history')}")
2302
+ for f_ in sm["families"]:
2303
+ if a.family and f_["family"] != a.family:
2304
+ continue
2305
+ print(f" {f_['family']}: {f_['n']} version(s)")
2306
+ for v in f_["versions"][: (50 if a.family else 3)]:
2307
+ print(f" {v['utc']} {v.get('report_id') or ''} reports/{v['dir']}")
2308
+ return 0
2229
2309
  elif a.cmd == "simulation":
2230
2310
  import json as _json
2231
2311
  from . import simulation as _SIM
@@ -9910,6 +9910,212 @@ def section_ck_operations_seismicity(tmp: str) -> None:
9910
9910
  + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
9911
9911
 
9912
9912
 
9913
+ def section_cl_history_and_dossier(tmp: str) -> None:
9914
+ """Section CL - report history and the signed dossier: Ed25519 against RFC 8032's test vectors and an independent
9915
+ implementation's signatures; every client report written in a workspace kept by version with its hashes, none kept
9916
+ outside one; a dossier signed, checked by its own standalone checker in a bare Python, and found INVALID when a file,
9917
+ the manifest or the signature is changed, and VALID BUT CHANGED when the source record or an earlier audit line is;
9918
+ the dossier chain; the API, the CLI, the Reports tab and the help pages."""
9919
+ import http.cookiejar
9920
+ import subprocess as _sp
9921
+ import urllib.request
9922
+ import urllib.error
9923
+ import zipfile
9924
+ import base64 as _b64
9925
+ import hashlib as _hl
9926
+ from . import __version__
9927
+ from . import client_reports as CRP
9928
+ from . import dossier as DZ
9929
+ from . import drill_string as DS
9930
+ from . import ed25519 as ED
9931
+ from . import helplib as H
9932
+ from . import report_history as RH
9933
+ from . import well_report as WR
9934
+ from .workspace import Workspace
9935
+ from .service import Service, Users
9936
+ pkg = Path(__file__).parent
9937
+ d = Path(tmp, "cl"); d.mkdir()
9938
+
9939
+ # -- CL1 the signature -------------------------------------------------------------------------------------------
9940
+ vec = [("9d61b19deffd5a60ba844af492ec2cc44449c5697b326919703bac031cae7f60", "d75a980182b10ab7d54bfed3c964073a0ee172f3daa62325af021a68f707511a", "",
9941
+ "e5564300c360ac729086e2cc806e828a84877f1eb8e5d974d873e065224901555fb8821590a33bacc61e39701cf9b46bd25bf5f0595bbe24655141438e7a100b"),
9942
+ ("4ccd089b28ff96da9db6c346ec114e0f5b8a319f35aba624da8cf6ed4fb8a6fb", "3d4017c3e843895a92b70aa74d1b7ebc9c982ccf2ec4968cc0cd55f12af4660c", "72",
9943
+ "92a009a9f0d4cab8720e820b5f642540a2b27b5416503f8fb3762223ebdb69da085ac1e43e15996e458f3613d0f11d8c387b2eaeb4302aeeb00d291612bb0c00"),
9944
+ ("c5aa8df43f9f837bedb7442f31dcb7b166d38535076f094b85ce3a2e0b4458f7", "fc51cd8e6218a1a38da47ed00230f0580816ed13ba3303ac5deb911548908025", "af82",
9945
+ "6291d657deec24024827e69c3abe01a30ce548a284743a445e3680d7db5ac3ac18ff9b538d16f290ae67f760984dc6594a7c15e9716ed28dc027beceea1ec40a")]
9946
+ rfc = all(ED.public_key(bytes.fromhex(s)).hex() == pk and ED.sign(bytes.fromhex(s), bytes.fromhex(m)).hex() == sg
9947
+ and ED.verify(bytes.fromhex(pk), bytes.fromhex(m), bytes.fromhex(sg)) for s, pk, m, sg in vec)
9948
+ s0, pk0, _, sg0 = vec[0]
9949
+ tamper = (not ED.verify(bytes.fromhex(pk0), b"x", bytes.fromhex(sg0)) and not ED.verify(bytes.fromhex(vec[1][1]), b"", bytes.fromhex(sg0))
9950
+ and not ED.verify(bytes.fromhex(pk0), b"", bytes.fromhex(sg0[:-2] + "0c")) and not ED.verify(b"\x00" * 31, b"", bytes.fromhex(sg0)))
9951
+ rng = __import__("random").Random(43)
9952
+ cross = True
9953
+ for _ in range(5): # random keys and messages: what is signed verifies, and nothing else does
9954
+ seed = bytes(rng.getrandbits(8) for _ in range(32)); msg = bytes(rng.getrandbits(8) for _ in range(77))
9955
+ sg_ = ED.sign(seed, msg)
9956
+ cross = cross and ED.verify(ED.public_key(seed), msg, sg_) and not ED.verify(ED.public_key(seed), msg[:-1], sg_)
9957
+ c1 = {"rfc8032": rfc, "tamper": tamper, "independent": cross, "fingerprint": ED.fingerprint(bytes.fromhex(pk0)).count(" ") == 7}
9958
+ failed1 = [k for k, v in c1.items() if not v]
9959
+ ok(not failed1,
9960
+ "CL1 Ed25519 in the standard library: RFC 8032's three test vectors signed and verified byte for byte; a changed message, another key, "
9961
+ "a changed signature byte and a malformed key refused; five random keys and messages verified, and refused once shortened"
9962
+ + (f" [failed: {', '.join(failed1)}]" if failed1 else ""))
9963
+
9964
+ # -- CL2 the report history --------------------------------------------------------------------------------------
9965
+ syn = DS.synthetic_motor_record(str(d))
9966
+ wsp = str(d / "ws"); ws = Workspace.create(wsp, "Pad CL", actor="tester")
9967
+ w = ws.add_well_file(syn["path"], display="SYNTHETIC well", actor="tester")
9968
+ ws.refresh_dashboard(actor="tester")
9969
+ r1 = WR.write_well(ws, w["id"], __version__, actor="tester")
9970
+ r2 = WR.write_well(ws, w["id"], __version__, actor="tester")
9971
+ WR.write_pad(ws, __version__, actor="tester")
9972
+ fam = f"wells/{w['id']}/well_report"
9973
+ vs = RH.entries(ws.reports_dir, fam)
9974
+ cur = Path(ws.reports_dir, "wells", w["id"], "well_report.html").read_bytes()
9975
+ newest = Path(ws.reports_dir, vs[0]["dir"], "well_report.html").read_bytes() if vs else b""
9976
+ oldest = json.loads(Path(ws.reports_dir, vs[-1]["dir"], "well_report.json").read_text(encoding="utf-8")) if vs else {}
9977
+ outside = d / "out"
9978
+ CRP.write(CRP.Document(title="SYNTHETIC outside", report_id="OUT-1", front=[], sections=[]), str(outside), basename="x")
9979
+ out_ok = RH.locate(str(outside)) is None and not (outside / "history").exists() and (outside / "x.html").is_file()
9980
+ sm = RH.summary(ws.reports_dir)
9981
+ pr = RH.prune(ws.reports_dir, 1, fam)
9982
+ c2 = {
9983
+ "versions": len(vs) == 2 and vs[0]["report_id"] == r2["report_id"] and vs[1]["report_id"] == r1["report_id"] and newest == cur
9984
+ and oldest.get("report_id", r1["report_id"]) == r1["report_id"],
9985
+ "hashes": all(_hl.sha256(Path(ws.reports_dir, v["dir"], f["name"]).read_bytes()).hexdigest() == f["sha256"] for v in vs for f in v["files"])
9986
+ and any(f["name"].endswith(".svg") for f in vs[0]["files"]),
9987
+ "families": {"pad_report", f"wells/{w['id']}/drilling_record"} <= {f["family"] for f in sm["families"]} and sm["bytes"] > 0,
9988
+ "outside": out_ok and RH.locate(str(pkg.parent / "docs" / "report_samples")) is None,
9989
+ "prune": not pr["applied"] and len(pr["removed"]) == 1 and len(RH.entries(ws.reports_dir, fam)) == 2,
9990
+ "listing": "history" not in CRP.__dict__ and all(not x.startswith("history") for x in os.listdir(Path(ws.reports_dir, "wells", w["id"]))),
9991
+ }
9992
+ failed2 = [k for k, v in c2.items() if not v]
9993
+ ok(not failed2,
9994
+ "CL2 report history: a well report written twice keeps both versions, newest first, by report id, the newest identical to the report at "
9995
+ "its usual path, every file's SHA-256 in the index (the drawings too); the pad report and the drilling record kept the same way; a "
9996
+ "report written outside a workspace keeps none; a prune names what it would remove and removes nothing until applied"
9997
+ + (f" [failed: {', '.join(failed2)}]" if failed2 else ""))
9998
+
9999
+ # -- CL3 the dossier ---------------------------------------------------------------------------------------------
10000
+ e1 = DZ.build(ws, "tester", __version__, w["id"])
10001
+ e2 = DZ.build(ws, "tester", __version__, None, include_sources=True)
10002
+ z1 = Path(ws.reports_dir, e1["path"]); z2 = Path(ws.reports_dir, e2["path"])
10003
+ v1 = DZ.verify(str(z1), ws)
10004
+ bare = d / "bare"; bare.mkdir()
10005
+ with zipfile.ZipFile(z1) as z:
10006
+ names = z.namelist(); man = json.loads(z.read("manifest.json")); z.extract("verify_dossier.py", str(bare))
10007
+ alone = _sp.run([sys.executable, "-I", str(bare / "verify_dossier.py"), str(z1)], capture_output=True, text=True, cwd=str(bare))
10008
+ alone_src = _sp.run([sys.executable, "-I", str(bare / "verify_dossier.py"), str(z1), "--sources", str(Path(wsp, "wells", w["id"], "source"))],
10009
+ capture_output=True, text=True, cwd=str(bare))
10010
+
10011
+ def rewrite(src, dst, change):
10012
+ with zipfile.ZipFile(src) as zi, zipfile.ZipFile(dst, "w", zipfile.ZIP_DEFLATED) as zo:
10013
+ for n in zi.namelist():
10014
+ b = zi.read(n)
10015
+ b2 = change(n, b)
10016
+ if b2 is not None:
10017
+ zo.writestr(n, b2)
10018
+ rep_name = next(f["path"] for f in man["files"] if f["path"].endswith("well_report.html"))
10019
+ rewrite(z1, d / "t_file.zip", lambda n, b: b.replace(b"SYNTHETIC", b"SYNTHETIX", 1) if n == rep_name else b)
10020
+ rewrite(z1, d / "t_man.zip", lambda n, b: b.replace(b'"created_by":"tester"', b'"created_by":"someone"') if n == "manifest.json" else b)
10021
+ rewrite(z1, d / "t_extra.zip", lambda n, b: b)
10022
+ with zipfile.ZipFile(d / "t_extra.zip", "a") as za:
10023
+ za.writestr("reports/extra.html", "<p>added</p>")
10024
+ rewrite(z1, d / "t_drop.zip", lambda n, b: None if n == rep_name else b)
10025
+ t_file, t_man, t_extra, t_drop = (DZ.verify(str(d / f"t_{k}.zip")) for k in ("file", "man", "extra", "drop"))
10026
+ other = Workspace.create(str(d / "ws2"), "Other", actor="tester")
10027
+ v_other = DZ.verify(str(z1), other)
10028
+ # the workspace moves on: the source record and an earlier audit line change
10029
+ src = next(Path(wsp, "wells", w["id"], "source").iterdir())
10030
+ raw = src.read_bytes()
10031
+ src.write_bytes(raw + b"\n")
10032
+ v_src = DZ.verify(str(z1), ws)
10033
+ src.write_bytes(raw)
10034
+ al = Path(ws.audit_path).read_text(encoding="utf-8").splitlines(keepends=True)
10035
+ Path(ws.audit_path).write_text("".join([al[0].replace("tester", "Tester", 1)] + al[1:]), encoding="utf-8")
10036
+ v_aud = DZ.verify(str(z1), ws)
10037
+ Path(ws.audit_path).write_text("".join(al), encoding="utf-8")
10038
+ v_back = DZ.verify(str(z1), ws)
10039
+ led = DZ.ledger(ws)
10040
+ with zipfile.ZipFile(z2) as z:
10041
+ man2 = json.loads(z.read("manifest.json"))
10042
+ ck = {c["check"]: c["ok"] for c in v_aud.get("workspace_checks", [])}
10043
+ c3 = {
10044
+ "valid": v1["status"] == "VALID" and all(c["ok"] for c in v1["workspace_checks"]) and v1["fingerprint"] == DZ.key_info(ws)["fingerprint"],
10045
+ "contents": {"manifest.json", "signature.json", "verify_dossier.py", "README.txt"} <= set(names) and any(n.startswith("audit/") for n in names)
10046
+ and any(n.startswith("declarations/wells/") for n in names) and not any(n.startswith("sources/") for n in names)
10047
+ and man["sources"] and man["sources"][0]["embedded"] is False and man["audit_chain"]["lines"] > 0
10048
+ and any(f.get("history") for f in man["files"] if f["role"] == "report") and not any("keys/" in n or "dossier_ed25519" in n for n in names),
10049
+ "standalone": alone.returncode == 0 and alone.stdout.startswith("VALID") and alone_src.returncode == 0 and "MATCH" in alone_src.stdout,
10050
+ "tamper": t_file["status"] == "INVALID" and not {c["check"]: c["ok"] for c in t_file["checks"]}["every file matches its hash"]
10051
+ and t_man["status"] == "INVALID" and not {c["check"]: c["ok"] for c in t_man["checks"]}["the signature verifies"]
10052
+ and t_extra["status"] == "INVALID" and "reports/extra.html" in {c["check"]: c["detail"] for c in t_extra["checks"]}["nothing unsigned rides along"]
10053
+ and t_drop["status"] == "INVALID",
10054
+ "other_site": v_other["status"].startswith("VALID, BUT") and not v_other["workspace_checks"][0]["ok"],
10055
+ "changed": v_src["status"] == "VALID, BUT THE WORKSPACE HAS CHANGED" and any(x["status"] == "DIFFERS" for x in v_src["workspace_sources"])
10056
+ and v_aud["status"] == "VALID, BUT THE WORKSPACE HAS CHANGED" and not ck["the audit log still holds what was chained"] and v_back["status"] == "VALID",
10057
+ "chain": len(led) == 2 and led[1]["previous"] == led[0]["dossier_id"] and man2["previous"]["manifest_sha256"] == led[0]["manifest_sha256"]
10058
+ and man2["scope"]["kind"] == "pad" and man2["sources"][0]["embedded"] and any(f["role"] == "source" for f in man2["files"])
10059
+ and any(a["action"] == "dossier.key.create" for a in ws.audit_log(500)) and sum(a["action"] == "dossier.sign" for a in ws.audit_log(500)) == 2,
10060
+ "evidence_pack_no_key": "dossier_ed25519" not in open(pkg / "files.py", encoding="utf-8").read(),
10061
+ }
10062
+ Users(str(Path(wsp, "users.json"))).add("v", "viewer-pass-1", "viewer"); Users(str(Path(wsp, "users.json"))).add("op", "op-pass-123", "operator")
10063
+ svc = Service(wsp, port=0, scheduler=False).start()
10064
+ base_url = svc.url.rstrip("/")
10065
+
10066
+ def opener(name, pw_):
10067
+ o = urllib.request.build_opener(urllib.request.HTTPCookieProcessor(http.cookiejar.CookieJar()))
10068
+ rq = urllib.request.Request(base_url + "/api/login", method="POST", data=json.dumps({"name": name, "password": pw_}).encode())
10069
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1"); o.open(rq, timeout=30)
10070
+ return o
10071
+
10072
+ def call(o, method, path, body=None, raw=False):
10073
+ rq = urllib.request.Request(base_url + path, method=method, data=(json.dumps(body).encode() if body is not None else None))
10074
+ if method == "POST":
10075
+ rq.add_header("Content-Type", "application/json"); rq.add_header("X-GEA-Action", "1")
10076
+ try:
10077
+ with o.open(rq, timeout=120) as rr_:
10078
+ b = rr_.read()
10079
+ return rr_.status, (b if raw else json.loads(b))
10080
+ except urllib.error.HTTPError as e:
10081
+ return e.code, (None if raw else json.loads(e.read()))
10082
+ try:
10083
+ V, O = opener("v", "viewer-pass-1"), opener("op", "op-pass-123")
10084
+ s_g, gj = call(V, "GET", "/api/dossier")
10085
+ s_vs, _ = call(V, "POST", "/api/dossier/sign", {"well_id": w["id"]})
10086
+ s_os, oj = call(O, "POST", "/api/dossier/sign", {"well_id": w["id"]})
10087
+ s_vv, vj = call(V, "POST", "/api/dossier/verify", {"path": e1["path"]})
10088
+ s_up, uj = call(V, "POST", "/api/dossier/verify", {"filename": "t.zip", "content_b64": _b64.b64encode((d / "t_file.zip").read_bytes()).decode()})
10089
+ s_bad, _ = call(V, "POST", "/api/dossier/verify", {"path": "../keys/dossier_ed25519.json"})
10090
+ s_nw, _ = call(O, "POST", "/api/dossier/sign", {"well_id": "nope"})
10091
+ s_dl, zb = call(V, "GET", "/reports/" + e1["path"], raw=True)
10092
+ s_hv, hb = call(V, "GET", "/reports/" + vs[0]["dir"] + "/well_report.html", raw=True)
10093
+ finally:
10094
+ svc.stop()
10095
+ cli_k = _sp.run([sys.executable, "-m", "gea", "dossier", "--workspace", wsp, "--key"], capture_output=True, text=True, cwd=str(pkg.parent))
10096
+ cli_s = _sp.run([sys.executable, "-m", "gea", "dossier", "--workspace", wsp], capture_output=True, text=True, cwd=str(pkg.parent))
10097
+ cli_v = _sp.run([sys.executable, "-m", "gea", "dossier", "--verify", str(d / "t_man.zip")], capture_output=True, text=True, cwd=str(pkg.parent))
10098
+ cli_h = _sp.run([sys.executable, "-m", "gea", "reports-history", "--workspace", wsp, "--family", fam], capture_output=True, text=True, cwd=str(pkg.parent))
10099
+ page_src = (pkg / "web" / "app.html").read_text(encoding="utf-8")
10100
+ c3.update({
10101
+ "api": s_g == 200 and gj["key"]["fingerprint"] == DZ.key_info(ws)["fingerprint"] and len(gj["dossiers"]) == 2 and gj["history"]["n_versions"] >= 3
10102
+ and s_vs == 403 and s_os == 200 and oj["previous"] == e2["dossier_id"] and s_vv == 200 and vj["status"] == "VALID" and s_up == 200 and uj["status"] == "INVALID"
10103
+ and s_bad == 400 and s_nw == 404 and s_dl == 200 and zb[:2] == b"PK" and s_hv == 200 and hb == newest,
10104
+ "cli": cli_k.returncode == 0 and DZ.key_info(ws)["fingerprint"] in cli_k.stdout and cli_s.returncode == 0 and "signed " in cli_s.stdout
10105
+ and cli_v.returncode == 1 and "the signature verifies" in cli_v.stdout and cli_h.returncode == 0 and r2["report_id"] in cli_h.stdout,
10106
+ "page": all(x in page_src for x in ("rpCards", "Signed dossiers", "/api/dossier/sign", "/api/dossier/verify", "Report history", "Check a dossier someone sent you")),
10107
+ "help": all(any(t[0] == k for t in H.INDEX) and not H.check(k) for k in ("report-history", "dossier")),
10108
+ })
10109
+ failed3 = [k for k, v in c3.items() if not v]
10110
+ ok(not failed3,
10111
+ "CL3 the signed dossier: a well's reports, declarations, audit log and source hashes signed with a key made on first use; its own "
10112
+ "checker run alone in a bare Python says VALID and matches the source record; a changed report, a changed manifest, a file added and "
10113
+ "a file removed each INVALID, naming the check; another site's workspace, a changed source record and a rewritten early audit line "
10114
+ "each VALID BUT THE WORKSPACE HAS CHANGED, and VALID again once restored; the pad's dossier with the sources embedded chained to the "
10115
+ "well's; the key never in a dossier; the API, the CLI, the Reports tab and the help pages"
10116
+ + (f" [failed: {', '.join(failed3)}]" if failed3 else ""))
10117
+
10118
+
9913
10119
  def main() -> int:
9914
10120
  print("GEA-Program - ACCEPTANCE SUITE (the product gate)")
9915
10121
  # a handle a job subprocess still holds at the end must not turn a finished gate into a traceback on Windows:
@@ -10002,6 +10208,7 @@ def main() -> int:
10002
10208
  section_ci_drill_string(tmp)
10003
10209
  section_cj_declarations(tmp)
10004
10210
  section_ck_operations_seismicity(tmp)
10211
+ section_cl_history_and_dossier(tmp)
10005
10212
  if _FAILS:
10006
10213
  print(f"[ACCEPTANCE] {len(_FAILS)} FAILURES ({_PASS} passed):")
10007
10214
  for f in _FAILS:
@@ -1925,4 +1925,9 @@ def write(doc: Document, out_dir: str, basename: str = 'gauge_drift_report') ->
1925
1925
  write_catalogue_csv(doc.data['_catalogue_obj'], paths['catalogue_csv'])
1926
1926
  else:
1927
1927
  paths.pop('catalogue_csv')
1928
+ try: # inside a workspace, keep the version this one replaces
1929
+ from . import report_history as _RH
1930
+ _RH.record(out_dir, basename, paths, getattr(doc, 'report_id', ''), getattr(doc, 'title', ''))
1931
+ except OSError:
1932
+ pass # a full disk loses the copy, never the report
1928
1933
  return paths