celatim 0.2.2__tar.gz → 0.2.3__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 (183) hide show
  1. {celatim-0.2.2 → celatim-0.2.3}/PKG-INFO +1 -1
  2. {celatim-0.2.2 → celatim-0.2.3}/pyproject.toml +1 -1
  3. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/__init__.py +1 -1
  4. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/adapter.py +2 -2
  5. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/analysis/__init__.py +12 -0
  6. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/analysis/crosshost_evidence.py +7 -4
  7. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/analysis/crosshost_metrics.py +46 -19
  8. celatim-0.2.3/src/celatim/analysis/detector_metrics.py +192 -0
  9. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/assurance/scorecard.py +135 -52
  10. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/catalog.py +2 -0
  11. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/cli.py +10 -1
  12. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/data/mechanisms.jsonl +9 -9
  13. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/discovery.py +4 -0
  14. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/docs/scenario-authoring.md +6 -0
  15. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/model.py +9 -0
  16. celatim-0.2.3/src/celatim/pdu/coap_msg.py +68 -0
  17. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/report/figures.py +20 -7
  18. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/report/macros.py +62 -7
  19. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/report/tables.py +22 -2
  20. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/coap_message.py +4 -5
  21. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/message_carrier.py +1 -1
  22. celatim-0.2.2/src/celatim/pdu/coap_msg.py +0 -53
  23. {celatim-0.2.2 → celatim-0.2.3}/LICENSE +0 -0
  24. {celatim-0.2.2 → celatim-0.2.3}/README.md +0 -0
  25. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/analysis/dataset.py +0 -0
  26. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/analysis/subliminal_controls.py +0 -0
  27. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/api.py +0 -0
  28. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/assurance/__init__.py +0 -0
  29. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/assurance/requirements.py +0 -0
  30. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/channel/__init__.py +0 -0
  31. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/channel/bits.py +0 -0
  32. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/channel/codec.py +0 -0
  33. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/channel/driver.py +0 -0
  34. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/channel/framer.py +0 -0
  35. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/channel/registry.py +0 -0
  36. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/cli_endpoints.py +0 -0
  37. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/crypto_transcript.py +0 -0
  38. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/data/__init__.py +0 -0
  39. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/data/protocol_rates.toml +0 -0
  40. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/detect/__init__.py +0 -0
  41. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/detect/replay.py +0 -0
  42. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/detect/rules.py +0 -0
  43. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/detect/scrub.py +0 -0
  44. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/detect/stateful.py +0 -0
  45. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/docs/__init__.py +0 -0
  46. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/docs/api-guide.md +0 -0
  47. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/docs/reviewer-quickstart.md +0 -0
  48. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/docs/troubleshooting.md +0 -0
  49. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/doctor.py +0 -0
  50. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/envelope.py +0 -0
  51. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/errors.py +0 -0
  52. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/evidence.py +0 -0
  53. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/evidence_index.py +0 -0
  54. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/inspection.py +0 -0
  55. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/layout/__init__.py +0 -0
  56. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/layout/loader.py +0 -0
  57. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/metrics/__init__.py +0 -0
  58. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/metrics/storage.py +0 -0
  59. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/metrics/subliminal.py +0 -0
  60. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/metrics/timing.py +0 -0
  61. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/observer.py +0 -0
  62. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pcap_decode.py +0 -0
  63. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/__init__.py +0 -0
  64. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/bgp_attr.py +0 -0
  65. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/bitfield.py +0 -0
  66. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/dns_txt.py +0 -0
  67. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/format_carriers.py +0 -0
  68. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/http2.py +0 -0
  69. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/http2_fields.py +0 -0
  70. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/minimal_pdu.py +0 -0
  71. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/quic.py +0 -0
  72. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/quic_fields.py +0 -0
  73. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/rtcp.py +0 -0
  74. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/scapy_field.py +0 -0
  75. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/scapy_pdu.py +0 -0
  76. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/ssh_kex.py +0 -0
  77. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/struct_fields.py +0 -0
  78. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/tcp.py +0 -0
  79. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/tls_fields.py +0 -0
  80. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/pdu/ws_frame.py +0 -0
  81. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/py.typed +0 -0
  82. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/report/__init__.py +0 -0
  83. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/report/detector_rules.py +0 -0
  84. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/report/guidance.py +0 -0
  85. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/report/protocol_rates.py +0 -0
  86. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/report/support_matrix.py +0 -0
  87. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/report/windows_capture.py +0 -0
  88. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/resources.py +0 -0
  89. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/reviewer_bundle.py +0 -0
  90. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenario.py +0 -0
  91. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/__init__.py +0 -0
  92. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/http2-ping-opaque.toml +0 -0
  93. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/quic-connection-id.toml +0 -0
  94. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/rtp-rtcp-ext-app.toml +0 -0
  95. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/subliminal-ctrl/zzz-ecdsa-nonce-128ctrl.toml +0 -0
  96. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/subliminal-ctrl/zzz-rsa-pss-salt-128ctrl.toml +0 -0
  97. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/tcp-reserved-bits.toml +0 -0
  98. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/z-edns0-padding-dnsmasq-dig.toml +0 -0
  99. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/z-http2-ping-opaque-hyper-h2.toml +0 -0
  100. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/z-http3-reserved-settings-aioquic.toml +0 -0
  101. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/z-quic-connection-id-aioquic.toml +0 -0
  102. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/z-tcp-reserved-bits-afpacket.toml +0 -0
  103. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/zz-ecdsa-nonce-local.toml +0 -0
  104. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/zz-rsa-pss-salt-local.toml +0 -0
  105. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/zzz-bgp-scapy.toml +0 -0
  106. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/zzz-coap-aiocoap.toml +0 -0
  107. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/zzz-dns-null-dnspython.toml +0 -0
  108. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/zzz-dns-txt-dnspython.toml +0 -0
  109. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/zzz-ssh-kexinit-paramiko.toml +0 -0
  110. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/scenarios/zzz-websocket-websockets.toml +0 -0
  111. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/__init__.py +0 -0
  112. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/carrier-endpoint-v1.schema.json +0 -0
  113. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/detector-replay-corpus-v1.schema.json +0 -0
  114. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/detector-replay-v1.schema.json +0 -0
  115. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/detector-trace-manifest-v1.schema.json +0 -0
  116. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/doctor-v1.schema.json +0 -0
  117. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/evidence-index-v1.schema.json +0 -0
  118. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/evidence-run-v1.schema.json +0 -0
  119. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/packet-service-preflight-v1.schema.json +0 -0
  120. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/packet-service-v1.schema.json +0 -0
  121. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/pcap-decode-v1.schema.json +0 -0
  122. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/provider-conformance-v1.schema.json +0 -0
  123. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/provider-inventory-v1.schema.json +0 -0
  124. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/provider-manifest-v1.schema.json +0 -0
  125. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/public-bundle-v1.schema.json +0 -0
  126. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/public-bundle-verify-v1.schema.json +0 -0
  127. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/public-evidence-index-v1.schema.json +0 -0
  128. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/qemu-tap-preflight-v1.schema.json +0 -0
  129. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/reviewer-bundle-v1.schema.json +0 -0
  130. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/reviewer-bundle-verify-v1.schema.json +0 -0
  131. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/scenario-execution-plan-v1.schema.json +0 -0
  132. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/scenario-inventory-v1.schema.json +0 -0
  133. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/scenario-v1.schema.json +0 -0
  134. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/scrub-report-v1.schema.json +0 -0
  135. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/support-matrix-v1.schema.json +0 -0
  136. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/testbed-requirements-v1.schema.json +0 -0
  137. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/timing-sweep-v1.schema.json +0 -0
  138. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/transfer-error-v1.schema.json +0 -0
  139. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/transfer-event-v1.schema.json +0 -0
  140. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/transfer-listener-status-v1.schema.json +0 -0
  141. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/transfer-listener-stop-v1.schema.json +0 -0
  142. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/transfer-manifest-v1.schema.json +0 -0
  143. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/transfer-offer-v1.schema.json +0 -0
  144. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/transfer-receipt-v1.schema.json +0 -0
  145. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/transfer-state-v1.schema.json +0 -0
  146. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/schemas/transfer-status-v1.schema.json +0 -0
  147. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/session.py +0 -0
  148. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/__init__.py +0 -0
  149. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/afpacket.py +0 -0
  150. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/bgp_message.py +0 -0
  151. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/commands.py +0 -0
  152. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/daemon.py +0 -0
  153. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/dns.py +0 -0
  154. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/dns_message.py +0 -0
  155. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/h3.py +0 -0
  156. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/http2.py +0 -0
  157. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/netns.py +0 -0
  158. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/packet_path.py +0 -0
  159. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/qemu.py +0 -0
  160. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/quic.py +0 -0
  161. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/requirements.py +0 -0
  162. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/ssh_message.py +0 -0
  163. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/tcpdump.py +0 -0
  164. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/testbed/ws_message.py +0 -0
  165. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/timing_sweep.py +0 -0
  166. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/__init__.py +0 -0
  167. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/carrier.py +0 -0
  168. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/cli.py +0 -0
  169. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/client.py +0 -0
  170. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/conformance.py +0 -0
  171. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/direct.py +0 -0
  172. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/errors.py +0 -0
  173. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/listener.py +0 -0
  174. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/models.py +0 -0
  175. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/packet_service.py +0 -0
  176. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/providers.py +0 -0
  177. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/security.py +0 -0
  178. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/server.py +0 -0
  179. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/source.py +0 -0
  180. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/state.py +0 -0
  181. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/storage.py +0 -0
  182. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transfer/wire.py +0 -0
  183. {celatim-0.2.2 → celatim-0.2.3}/src/celatim/transports.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: celatim
3
- Version: 0.2.2
3
+ Version: 0.2.3
4
4
  Summary: Typed secure file transfer and reproducible covert-channel measurement for IETF protocol fields.
5
5
  Keywords: covert-channels,network-measurement,network-protocols,privacy,steganography
6
6
  Author: Michael Bommarito
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "celatim"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Typed secure file transfer and reproducible covert-channel measurement for IETF protocol fields."
5
5
  readme = "README.md"
6
6
  license = "Apache-2.0"
@@ -361,7 +361,7 @@ from .transports import (
361
361
  extract_pcap_carriers,
362
362
  )
363
363
 
364
- __version__ = "0.2.2"
364
+ __version__ = "0.2.3"
365
365
 
366
366
  __all__ = [
367
367
  "BUNDLE_SCHEMA_VERSION",
@@ -549,12 +549,12 @@ def _paths_for(
549
549
  kind=AdapterPathKind.COAP_AIOCOAP,
550
550
  transport_kind="coap_aiocoap",
551
551
  evidence_tier="real_daemon_path",
552
- claim_status="local_aiocoap_client_server_payload_message_path",
552
+ claim_status="local_aiocoap_client_server_elective_option_path",
553
553
  required_extras=("iot",),
554
554
  records_artifact=True,
555
555
  notes=(
556
556
  "paired aiocoap client/server CoAP message exchange; covert bytes in "
557
- "the conforming payload, aiocoap Message codec validates"
557
+ "an unknown elective option, aiocoap Message codec validates"
558
558
  ),
559
559
  )
560
560
  )
@@ -18,6 +18,13 @@ from .dataset import (
18
18
  build_records,
19
19
  write_dataset,
20
20
  )
21
+ from .detector_metrics import (
22
+ DETECTOR_METRICS_SCHEMA_VERSION,
23
+ DetectorMetricReport,
24
+ LabeledDetectionScore,
25
+ ThresholdMetrics,
26
+ detector_metric_report,
27
+ )
21
28
  from .subliminal_controls import (
22
29
  SUBLIMINAL_CONTROL_REPORT_SCHEMA_VERSION,
23
30
  build_subliminal_control_report,
@@ -27,14 +34,19 @@ __all__ = [
27
34
  "CLAIM_LEDGER_SCHEMA_VERSION",
28
35
  "CROSSHOST_PUBLIC_INDEX_SCHEMA_VERSION",
29
36
  "DATASET_SCHEMA_VERSION",
37
+ "DETECTOR_METRICS_SCHEMA_VERSION",
30
38
  "SUBLIMINAL_CONTROL_REPORT_SCHEMA_VERSION",
31
39
  "DatasetRecord",
40
+ "DetectorMetricReport",
41
+ "LabeledDetectionScore",
42
+ "ThresholdMetrics",
32
43
  "build_claim_ledger",
33
44
  "build_crosshost_public_index",
34
45
  "build_manifest",
35
46
  "build_records",
36
47
  "build_subliminal_control_report",
37
48
  "claim_count",
49
+ "detector_metric_report",
38
50
  "load_claim_ledger",
39
51
  "load_crosshost_public_index",
40
52
  "write_dataset",
@@ -334,11 +334,13 @@ def _public_metric_record(record: Mapping[str, Any]) -> dict[str, Any]:
334
334
  "recovered_bytes": record.get("recovered_bytes"),
335
335
  "carrier_units": record.get("carrier_units"),
336
336
  "raw_capacity_bits_per_unit": record.get("raw_capacity_bits_per_unit"),
337
- "carrier_bit_efficiency": record.get("carrier_bit_efficiency"),
337
+ "packing_efficiency_diagnostic": record.get("packing_efficiency_diagnostic"),
338
338
  "method_wire_bytes": record.get("method_wire_bytes"),
339
339
  "method_wire_basis": record.get("method_wire_basis"),
340
- "method_wire_overhead": record.get("method_wire_overhead"),
341
- "payload_to_method_wire_ratio": record.get("payload_to_method_wire_ratio"),
340
+ "wire_expansion": record.get("wire_expansion"),
341
+ "useful_payload_ratio": record.get("useful_payload_ratio"),
342
+ "carrier_units_per_payload_byte": record.get("carrier_units_per_payload_byte"),
343
+ "method_wire_bytes_per_carrier_unit": record.get("method_wire_bytes_per_carrier_unit"),
342
344
  "vxlan_underlay_bytes_no_fcs": record.get("vxlan_underlay_bytes_no_fcs"),
343
345
  "payload_to_vxlan_underlay_ratio": record.get("payload_to_vxlan_underlay_ratio"),
344
346
  "timing": {
@@ -347,7 +349,8 @@ def _public_metric_record(record: Mapping[str, Any]) -> dict[str, Any]:
347
349
  "measured_window_basis": timing_map.get("measured_window_basis"),
348
350
  "observed_unit_rate_hz": timing_map.get("observed_unit_rate_hz"),
349
351
  "payload_bytes_per_s": timing_map.get("payload_bytes_per_s"),
350
- "payload_bits_per_s": timing_map.get("payload_bits_per_s"),
352
+ "observed_recovery_rate_bps": timing_map.get("observed_recovery_rate_bps"),
353
+ "native_goodput_bps": timing_map.get("native_goodput_bps"),
351
354
  "scheduled_unit_rate_hz": timing_map.get("scheduled_unit_rate_hz"),
352
355
  "scheduled_duration_s": timing_map.get("scheduled_duration_s"),
353
356
  },
@@ -7,7 +7,8 @@ from collections.abc import Iterable, Mapping, Sequence
7
7
  from dataclasses import dataclass
8
8
  from typing import Any
9
9
 
10
- METRICS_SCHEMA_VERSION = "celatim.alice_bob_metrics.v1"
10
+ METRICS_SCHEMA_VERSION = "celatim.alice_bob_metrics.v2"
11
+ NATIVE_GOODPUT_CLAIM_STATUS = "native_protocol_goodput_measured"
11
12
 
12
13
  ETHERNET_HEADER_BYTES = 14
13
14
  IPV4_HEADER_BYTES = 20
@@ -74,6 +75,11 @@ def metric_record(input: MechanismMetricInput) -> dict[str, Any]:
74
75
  if input.carrier_units is not None and input.raw_capacity_bits is not None
75
76
  else None
76
77
  )
78
+ observed_recovery_rate_bps = (
79
+ _ratio(input.recovered_bytes * 8, input.measured_window_s)
80
+ if input.recovered_bytes is not None
81
+ else None
82
+ )
77
83
  return {
78
84
  "mechanism": input.mechanism_id,
79
85
  "suite": input.suite,
@@ -84,13 +90,15 @@ def metric_record(input: MechanismMetricInput) -> dict[str, Any]:
84
90
  "carrier_units": input.carrier_units,
85
91
  "raw_capacity_bits_per_unit": input.raw_capacity_bits,
86
92
  "carrier_capacity_bits": carrier_capacity_bits,
87
- "carrier_bit_efficiency": _ratio(payload_bits, carrier_capacity_bits),
93
+ "packing_efficiency_diagnostic": _ratio(payload_bits, carrier_capacity_bits),
88
94
  "carrier_wire_bytes": input.carrier_wire_bytes,
89
95
  "method_wire_bytes": input.method_wire_bytes,
90
96
  "method_wire_basis": input.method_wire_basis,
91
97
  "vxlan_underlay_bytes_no_fcs": input.vxlan_underlay_bytes_no_fcs,
92
- "payload_to_method_wire_ratio": _ratio(input.payload_bytes, input.method_wire_bytes),
93
- "method_wire_overhead": _ratio(input.method_wire_bytes, input.payload_bytes),
98
+ "useful_payload_ratio": _ratio(input.payload_bytes, input.method_wire_bytes),
99
+ "wire_expansion": _ratio(input.method_wire_bytes, input.payload_bytes),
100
+ "carrier_units_per_payload_byte": _ratio(input.carrier_units, input.payload_bytes),
101
+ "method_wire_bytes_per_carrier_unit": _ratio(input.method_wire_bytes, input.carrier_units),
94
102
  "payload_to_vxlan_underlay_ratio": _ratio(
95
103
  input.payload_bytes, input.vxlan_underlay_bytes_no_fcs
96
104
  ),
@@ -102,9 +110,10 @@ def metric_record(input: MechanismMetricInput) -> dict[str, Any]:
102
110
  "scheduled_duration_s": input.scheduled_duration_s,
103
111
  "observed_unit_rate_hz": _ratio(input.carrier_units, input.measured_window_s),
104
112
  "payload_bytes_per_s": _ratio(input.recovered_bytes, input.measured_window_s),
105
- "payload_bits_per_s": (
106
- _ratio(input.recovered_bytes * 8, input.measured_window_s)
107
- if input.recovered_bytes is not None
113
+ "observed_recovery_rate_bps": observed_recovery_rate_bps,
114
+ "native_goodput_bps": (
115
+ observed_recovery_rate_bps
116
+ if input.timing_claim_status == NATIVE_GOODPUT_CLAIM_STATUS
108
117
  else None
109
118
  ),
110
119
  "claim_status": input.timing_claim_status,
@@ -123,7 +132,7 @@ def metrics_summary(
123
132
  measured = [
124
133
  record
125
134
  for record in pass_records
126
- if _nested_number(record, ("timing", "payload_bits_per_s")) is not None
135
+ if _nested_number(record, ("timing", "observed_recovery_rate_bps")) is not None
127
136
  ]
128
137
  return {
129
138
  "schema_version": METRICS_SCHEMA_VERSION,
@@ -134,23 +143,27 @@ def metrics_summary(
134
143
  "timing_claim_status_counts": _counts(
135
144
  _nested_value(record, ("timing", "claim_status")) for record in records
136
145
  ),
137
- "method_wire_overhead": _stats(
138
- _number(record.get("method_wire_overhead")) for record in pass_records
146
+ "wire_expansion": _stats(_number(record.get("wire_expansion")) for record in pass_records),
147
+ "useful_payload_ratio": _stats(
148
+ _number(record.get("useful_payload_ratio")) for record in pass_records
149
+ ),
150
+ "carrier_units_per_payload_byte": _stats(
151
+ _number(record.get("carrier_units_per_payload_byte")) for record in pass_records
139
152
  ),
140
- "payload_to_method_wire_ratio": _stats(
141
- _number(record.get("payload_to_method_wire_ratio")) for record in pass_records
153
+ "observed_recovery_rate_bps": _stats(
154
+ _nested_number(record, ("timing", "observed_recovery_rate_bps")) for record in measured
142
155
  ),
143
- "payload_bits_per_s": _stats(
144
- _nested_number(record, ("timing", "payload_bits_per_s")) for record in measured
156
+ "native_goodput_bps": _stats(
157
+ _nested_number(record, ("timing", "native_goodput_bps")) for record in measured
145
158
  ),
146
159
  "observed_unit_rate_hz": _stats(
147
160
  _nested_number(record, ("timing", "observed_unit_rate_hz")) for record in measured
148
161
  ),
149
- "fastest_payload_bits_per_s": _extreme(
150
- measured, ("timing", "payload_bits_per_s"), reverse=True
162
+ "fastest_observed_recovery_rate_bps": _extreme(
163
+ measured, ("timing", "observed_recovery_rate_bps"), reverse=True
151
164
  ),
152
- "slowest_payload_bits_per_s": _extreme(
153
- measured, ("timing", "payload_bits_per_s"), reverse=False
165
+ "slowest_observed_recovery_rate_bps": _extreme(
166
+ measured, ("timing", "observed_recovery_rate_bps"), reverse=False
154
167
  ),
155
168
  }
156
169
 
@@ -191,10 +204,13 @@ def _counts(values: Iterable[Any]) -> dict[str, int]:
191
204
  def _stats(values: Iterable[float | None]) -> dict[str, float | None]:
192
205
  series = sorted(value for value in values if value is not None)
193
206
  if not series:
194
- return {"min": None, "median": None, "max": None}
207
+ return {"n": 0, "min": None, "q1": None, "median": None, "q3": None, "max": None}
195
208
  return {
209
+ "n": len(series),
196
210
  "min": series[0],
211
+ "q1": _quantile(series, 0.25),
197
212
  "median": _median(series),
213
+ "q3": _quantile(series, 0.75),
198
214
  "max": series[-1],
199
215
  }
200
216
 
@@ -206,6 +222,16 @@ def _median(series: Sequence[float]) -> float:
206
222
  return (series[midpoint - 1] + series[midpoint]) / 2.0
207
223
 
208
224
 
225
+ def _quantile(series: Sequence[float], probability: float) -> float:
226
+ position = (len(series) - 1) * probability
227
+ lower = math.floor(position)
228
+ upper = math.ceil(position)
229
+ if lower == upper:
230
+ return series[lower]
231
+ fraction = position - lower
232
+ return series[lower] + fraction * (series[upper] - series[lower])
233
+
234
+
209
235
  def _extreme(
210
236
  records: Sequence[Mapping[str, Any]],
211
237
  path: Sequence[str],
@@ -227,6 +253,7 @@ def _extreme(
227
253
 
228
254
  __all__ = [
229
255
  "METRICS_SCHEMA_VERSION",
256
+ "NATIVE_GOODPUT_CLAIM_STATUS",
230
257
  "MechanismMetricInput",
231
258
  "carrier_lengths_from_envelope",
232
259
  "metric_record",
@@ -0,0 +1,192 @@
1
+ """Threshold-sweep metrics for labeled detector scores."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import math
6
+ from collections.abc import Iterable
7
+ from dataclasses import dataclass
8
+ from itertools import pairwise
9
+ from typing import Any
10
+
11
+ DETECTOR_METRICS_SCHEMA_VERSION = "celatim.detector_metrics.v1"
12
+
13
+
14
+ @dataclass(frozen=True)
15
+ class LabeledDetectionScore:
16
+ label: bool
17
+ score: float
18
+
19
+ def __post_init__(self) -> None:
20
+ if not math.isfinite(self.score):
21
+ raise ValueError("detector score must be finite")
22
+
23
+
24
+ @dataclass(frozen=True)
25
+ class ThresholdMetrics:
26
+ threshold: float
27
+ true_positive: int
28
+ false_positive: int
29
+ true_negative: int
30
+ false_negative: int
31
+ true_positive_rate: float
32
+ false_positive_rate: float
33
+ precision: float | None
34
+ recall: float
35
+ tpr_wilson95: tuple[float, float]
36
+ fpr_wilson95: tuple[float, float]
37
+ prevalence_adjusted_precision: dict[str, float | None]
38
+
39
+ def to_json(self) -> dict[str, Any]:
40
+ return {
41
+ "threshold": self.threshold,
42
+ "true_positive": self.true_positive,
43
+ "false_positive": self.false_positive,
44
+ "true_negative": self.true_negative,
45
+ "false_negative": self.false_negative,
46
+ "true_positive_rate": self.true_positive_rate,
47
+ "false_positive_rate": self.false_positive_rate,
48
+ "precision": self.precision,
49
+ "recall": self.recall,
50
+ "tpr_wilson95": list(self.tpr_wilson95),
51
+ "fpr_wilson95": list(self.fpr_wilson95),
52
+ "prevalence_adjusted_precision": self.prevalence_adjusted_precision,
53
+ }
54
+
55
+
56
+ @dataclass(frozen=True)
57
+ class DetectorMetricReport:
58
+ positive_count: int
59
+ negative_count: int
60
+ roc_auc: float
61
+ average_precision: float
62
+ prevalence_assumptions: tuple[float, ...]
63
+ thresholds: tuple[ThresholdMetrics, ...]
64
+
65
+ def to_json(self) -> dict[str, Any]:
66
+ return {
67
+ "schema_version": DETECTOR_METRICS_SCHEMA_VERSION,
68
+ "positive_count": self.positive_count,
69
+ "negative_count": self.negative_count,
70
+ "roc_auc": self.roc_auc,
71
+ "average_precision": self.average_precision,
72
+ "prevalence_assumptions": list(self.prevalence_assumptions),
73
+ "thresholds": [row.to_json() for row in self.thresholds],
74
+ }
75
+
76
+
77
+ def detector_metric_report(
78
+ scores: Iterable[LabeledDetectionScore],
79
+ *,
80
+ prevalence_assumptions: tuple[float, ...] = (0.0001, 0.001, 0.01),
81
+ ) -> DetectorMetricReport:
82
+ observations = tuple(scores)
83
+ positives = sum(observation.label for observation in observations)
84
+ negatives = len(observations) - positives
85
+ if positives == 0 or negatives == 0:
86
+ raise ValueError("detector evaluation requires positive and negative observations")
87
+ if any(not 0 < prevalence < 1 for prevalence in prevalence_assumptions):
88
+ raise ValueError("prevalence assumptions must be between zero and one")
89
+ unique_scores = sorted({observation.score for observation in observations}, reverse=True)
90
+ thresholds = (math.inf, *unique_scores, -math.inf)
91
+ rows = tuple(
92
+ _threshold_metrics(
93
+ observations,
94
+ threshold,
95
+ prevalence_assumptions=prevalence_assumptions,
96
+ )
97
+ for threshold in thresholds
98
+ )
99
+ roc_points = sorted(
100
+ ((row.false_positive_rate, row.true_positive_rate) for row in rows),
101
+ key=lambda point: (point[0], point[1]),
102
+ )
103
+ precision_rows = [row for row in rows if row.precision is not None]
104
+ precision_rows.sort(key=lambda row: row.recall)
105
+ return DetectorMetricReport(
106
+ positive_count=positives,
107
+ negative_count=negatives,
108
+ roc_auc=_trapezoid(roc_points),
109
+ average_precision=_step_average_precision(precision_rows),
110
+ prevalence_assumptions=prevalence_assumptions,
111
+ thresholds=rows,
112
+ )
113
+
114
+
115
+ def _threshold_metrics(
116
+ scores: tuple[LabeledDetectionScore, ...],
117
+ threshold: float,
118
+ *,
119
+ prevalence_assumptions: tuple[float, ...],
120
+ ) -> ThresholdMetrics:
121
+ true_positive = sum(item.label and item.score >= threshold for item in scores)
122
+ false_positive = sum(not item.label and item.score >= threshold for item in scores)
123
+ positive_count = sum(item.label for item in scores)
124
+ negative_count = len(scores) - positive_count
125
+ false_negative = positive_count - true_positive
126
+ true_negative = negative_count - false_positive
127
+ tpr = true_positive / positive_count
128
+ fpr = false_positive / negative_count
129
+ predicted_positive = true_positive + false_positive
130
+ precision = true_positive / predicted_positive if predicted_positive else None
131
+ adjusted = {
132
+ f"{prevalence:g}": _prevalence_precision(tpr, fpr, prevalence)
133
+ for prevalence in prevalence_assumptions
134
+ }
135
+ return ThresholdMetrics(
136
+ threshold=threshold,
137
+ true_positive=true_positive,
138
+ false_positive=false_positive,
139
+ true_negative=true_negative,
140
+ false_negative=false_negative,
141
+ true_positive_rate=tpr,
142
+ false_positive_rate=fpr,
143
+ precision=precision,
144
+ recall=tpr,
145
+ tpr_wilson95=_wilson95(true_positive, positive_count),
146
+ fpr_wilson95=_wilson95(false_positive, negative_count),
147
+ prevalence_adjusted_precision=adjusted,
148
+ )
149
+
150
+
151
+ def _prevalence_precision(tpr: float, fpr: float, prevalence: float) -> float | None:
152
+ denominator = tpr * prevalence + fpr * (1 - prevalence)
153
+ return tpr * prevalence / denominator if denominator else None
154
+
155
+
156
+ def _wilson95(successes: int, trials: int) -> tuple[float, float]:
157
+ z = 1.959963984540054
158
+ proportion = successes / trials
159
+ denominator = 1 + z * z / trials
160
+ center = (proportion + z * z / (2 * trials)) / denominator
161
+ margin = (
162
+ z
163
+ * math.sqrt(proportion * (1 - proportion) / trials + z * z / (4 * trials * trials))
164
+ / denominator
165
+ )
166
+ return max(0.0, center - margin), min(1.0, center + margin)
167
+
168
+
169
+ def _trapezoid(points: list[tuple[float, float]]) -> float:
170
+ area = 0.0
171
+ for left, right in pairwise(points):
172
+ area += (right[0] - left[0]) * (left[1] + right[1]) / 2
173
+ return area
174
+
175
+
176
+ def _step_average_precision(rows: list[ThresholdMetrics]) -> float:
177
+ area = 0.0
178
+ previous_recall = 0.0
179
+ for row in rows:
180
+ if row.recall > previous_recall and row.precision is not None:
181
+ area += (row.recall - previous_recall) * row.precision
182
+ previous_recall = row.recall
183
+ return area
184
+
185
+
186
+ __all__ = [
187
+ "DETECTOR_METRICS_SCHEMA_VERSION",
188
+ "DetectorMetricReport",
189
+ "LabeledDetectionScore",
190
+ "ThresholdMetrics",
191
+ "detector_metric_report",
192
+ ]