arbiter-engine 0.2.8__tar.gz → 0.2.9__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 (334) hide show
  1. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/CHANGELOG.md +20 -0
  2. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/CITATION.cff +1 -1
  3. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/PKG-INFO +1 -1
  4. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/api.py +11 -2
  5. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/history/calendar.py +5 -0
  6. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/history/sqlite_store.py +18 -0
  7. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/residual/predict_vs_mirror.py +42 -13
  8. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/pyproject.toml +1 -1
  9. arbiter_engine-0.2.9/tests/test_every_history_the_engine_ships_can_be_described.py +260 -0
  10. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/.gitattributes +0 -0
  11. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  12. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  13. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/.github/workflows/release.yml +0 -0
  14. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/.github/workflows/tests.yml +0 -0
  15. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/.github/workflows/verify-published.yml +0 -0
  16. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/.github/workflows/verify-tag-artifact.yml +0 -0
  17. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/.gitignore +0 -0
  18. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/AI_ATTRIBUTION.md +0 -0
  19. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/AUTHORS.md +0 -0
  20. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/BRIDGES.md +0 -0
  21. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/CODE_OF_CONDUCT.md +0 -0
  22. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/COMPATIBILITY.md +0 -0
  23. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/CONTRIBUTING.md +0 -0
  24. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/LICENSE +0 -0
  25. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/MODELING.md +0 -0
  26. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/NOTICE +0 -0
  27. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/PRIVACY.md +0 -0
  28. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/README.md +0 -0
  29. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/ROADMAP.md +0 -0
  30. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/SECURITY.md +0 -0
  31. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/STANCE.md +0 -0
  32. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/SUPPORT.md +0 -0
  33. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/TRADEMARK.md +0 -0
  34. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/__init__.py +0 -0
  35. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/assumptions.py +0 -0
  36. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/axiom_thresholds.py +0 -0
  37. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/causal/__init__.py +0 -0
  38. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/causal/discovery.py +0 -0
  39. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/causal/granger.py +0 -0
  40. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/causal/leadlag.py +0 -0
  41. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/clock.py +0 -0
  42. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/derived/__init__.py +0 -0
  43. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/derived/indicator.py +0 -0
  44. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/derived/parser.py +0 -0
  45. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/envelope.py +0 -0
  46. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/examples/battery_pack.yaml +0 -0
  47. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/examples/factory_line.yaml +0 -0
  48. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/examples/kubernetes_node.yaml +0 -0
  49. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/examples/margin_book.yaml +0 -0
  50. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/examples/pump_tank_dynamics.yaml +0 -0
  51. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/examples/pump_tank_planning.yaml +0 -0
  52. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/examples/substation_feeder.yaml +0 -0
  53. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/examples/substation_feeder_surprises.yaml +0 -0
  54. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/examples/water_tank.yaml +0 -0
  55. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/fire_frequency.py +0 -0
  56. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/forecast/__init__.py +0 -0
  57. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/forecast/contract.py +0 -0
  58. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/forecast/envelope.py +0 -0
  59. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/forecast/ingest.py +0 -0
  60. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/forecast/monitor.py +0 -0
  61. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/forecast/shadow.py +0 -0
  62. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/history/__init__.py +0 -0
  63. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/history/observation.py +0 -0
  64. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/history/observation_production.py +0 -0
  65. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/history/observation_source_wiring.py +0 -0
  66. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/history/readiness.py +0 -0
  67. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/inference/__init__.py +0 -0
  68. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/inference/causal.py +0 -0
  69. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/inference/hypothesis.py +0 -0
  70. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/inference/runner.py +0 -0
  71. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/inference/ve.py +0 -0
  72. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/interfaces.py +0 -0
  73. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/mcp/__init__.py +0 -0
  74. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/mcp/server.py +0 -0
  75. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/__init__.py +0 -0
  76. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axiom_verdicts_production.py +0 -0
  77. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/__init__.py +0 -0
  78. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/boundedness.py +0 -0
  79. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/connectivity.py +0 -0
  80. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/conservation.py +0 -0
  81. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/consistency.py +0 -0
  82. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/extensions.py +0 -0
  83. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/homeostasis.py +0 -0
  84. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/monotonicity.py +0 -0
  85. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/peers.py +0 -0
  86. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/responsiveness.py +0 -0
  87. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/roles.py +0 -0
  88. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/stability.py +0 -0
  89. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/domain_loader.py +0 -0
  90. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/entail.py +0 -0
  91. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/loader.py +0 -0
  92. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/prediction_production.py +0 -0
  93. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/reasoner.py +0 -0
  94. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/ontology/schemas/health_meta_ontology.ttl +0 -0
  95. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/producers/__init__.py +0 -0
  96. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/producers/baseline_learner.py +0 -0
  97. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/projection/__init__.py +0 -0
  98. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/projection/projector.py +0 -0
  99. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/projection/runner.py +0 -0
  100. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/propagation/__init__.py +0 -0
  101. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/propagation/impact_estimator.py +0 -0
  102. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/propagation/lp_confidence.py +0 -0
  103. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/propagation/mcts_root_cause.py +0 -0
  104. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/propagation/root_cause.py +0 -0
  105. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/propagation/weight_learner.py +0 -0
  106. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/rca/__init__.py +0 -0
  107. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/rca/greedy_set_cover.py +0 -0
  108. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/replay.py +0 -0
  109. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/residual/__init__.py +0 -0
  110. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/residual/sqlite_ledger.py +0 -0
  111. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/schema/envelope.schema.json +0 -0
  112. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/scripts/__init__.py +0 -0
  113. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/scripts/benchmark_check.py +0 -0
  114. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/scripts/surprise_benchmark.py +0 -0
  115. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/subenvelope.py +0 -0
  116. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/surprises.py +0 -0
  117. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/temporal/__init__.py +0 -0
  118. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/temporal/temporal_edge.py +0 -0
  119. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/temporal/trend_projection.py +0 -0
  120. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/__init__.py +0 -0
  121. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/action_clears_problem.py +0 -0
  122. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/actions.py +0 -0
  123. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/builder.py +0 -0
  124. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/gap.py +0 -0
  125. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/hypothesis_generator.py +0 -0
  126. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/hypothesis_production.py +0 -0
  127. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/kernel_pipeline_executor.py +0 -0
  128. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/monte_carlo_predictor.py +0 -0
  129. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/optimization_production.py +0 -0
  130. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/pipeline_production.py +0 -0
  131. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/planner.py +0 -0
  132. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/rollout.py +0 -0
  133. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/topology.py +0 -0
  134. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/topology_optimizer.py +0 -0
  135. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/transition_learner.py +0 -0
  136. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/traverser.py +0 -0
  137. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/twin/traverser_production.py +0 -0
  138. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/arbiter_engine/types.py +0 -0
  139. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/evidence/README.md +0 -0
  140. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/evidence/alpha1_evidence_pack.md +0 -0
  141. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/evidence/customer_deployment_runbook.md +0 -0
  142. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/evidence/extended_fault_scenarios.md +0 -0
  143. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/evidence/l5_surprise_synthesis.md +0 -0
  144. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/evidence/observability_handoff_guide.md +0 -0
  145. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/evidence/tech_brief.md +0 -0
  146. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/evidence/use-case-catalogue.md +0 -0
  147. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/examples/battery_pack.yaml +0 -0
  148. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/examples/factory_line.yaml +0 -0
  149. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/examples/kubernetes_node.yaml +0 -0
  150. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/examples/margin_book.yaml +0 -0
  151. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/examples/pump_tank_dynamics.yaml +0 -0
  152. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/examples/pump_tank_planning.yaml +0 -0
  153. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/examples/substation_feeder.yaml +0 -0
  154. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/examples/substation_feeder_surprises.yaml +0 -0
  155. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/examples/water_tank.yaml +0 -0
  156. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/schema/envelope.schema.json +0 -0
  157. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/conftest.py +0 -0
  158. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_balance_may_cross_an_entity_boundary.py +0 -0
  159. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_bound_can_belong_to_one_instance.py +0 -0
  160. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_calibration_figure_names_its_population.py +0 -0
  161. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_calibration_says_how_many_trajectories_it_saw.py +0 -0
  162. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_chain_of_lags_says_how_it_was_composed.py +0 -0
  163. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_changelog_section_says_whether_you_can_install_it.py +0 -0
  164. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_check_that_cannot_run_says_so.py +0 -0
  165. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_check_that_declines_is_heard.py +0 -0
  166. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_checker_asks_no_entity_type.py +0 -0
  167. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_clean_rollout_still_counts_what_it_evaluated.py +0 -0
  168. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_companion_beside_the_models_is_not_one.py +0 -0
  169. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_correct_gain_is_not_contradicted_by_a_lagged_series.py +0 -0
  170. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_coupling_block_is_checked_too.py +0 -0
  171. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_coupling_is_graded_on_its_own_projections.py +0 -0
  172. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_coupling_learns_how_its_projections_fared.py +0 -0
  173. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_declared_bad_state_is_not_silent.py +0 -0
  174. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_declared_calendar_is_actually_read.py +0 -0
  175. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_declared_edge_reaches_the_published_surface.py +0 -0
  176. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_declared_floor_is_checked_on_every_path.py +0 -0
  177. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_declared_gain_moves_the_downstream_value.py +0 -0
  178. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_declared_number_is_the_first_breach.py +0 -0
  179. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_declared_offset_waits_for_the_delay.py +0 -0
  180. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_declared_source_matches_the_guide.py +0 -0
  181. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_declared_spread_reaches_the_value.py +0 -0
  182. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_decline_states_the_number_it_computed.py +0 -0
  183. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_derived_indicator_is_computed_not_fed.py +0 -0
  184. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_discipline_reports_its_own_denominator.py +0 -0
  185. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_dropped_declaration_is_not_silent.py +0 -0
  186. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_filed_source_says_who_is_not_a_producer.py +0 -0
  187. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_finding_says_where_to_look_next.py +0 -0
  188. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecast_batch_is_filed_or_accounted_for.py +0 -0
  189. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecast_check_that_refused_says_so_out_loud.py +0 -0
  190. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecast_is_graded_at_its_horizon.py +0 -0
  191. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecast_is_measured_against_a_random_walk.py +0 -0
  192. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecast_is_scored_not_just_stored.py +0 -0
  193. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecast_keeps_its_doubt.py +0 -0
  194. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecast_keeps_its_doubt_when_its_median_stands_still.py +0 -0
  195. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecast_states_its_own_distribution.py +0 -0
  196. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecast_widens_and_says_what_it_assumed.py +0 -0
  197. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_forecaster_is_judged_by_the_same_eight_axioms.py +0 -0
  198. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_freeze_is_found_inside_its_window.py +0 -0
  199. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_latency_without_a_threshold_says_so.py +0 -0
  200. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_lazy_import_did_not_move_a_name.py +0 -0
  201. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_lead_lag_p_value_means_what_it_says.py +0 -0
  202. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_learned_gain_is_a_proposal.py +0 -0
  203. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_margin_is_measured_against_every_line.py +0 -0
  204. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_nested_block_is_checked_too.py +0 -0
  205. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_pair_can_beat_the_best_single_action.py +0 -0
  206. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_per_instance_bound_reaches_every_reader.py +0 -0
  207. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_plan_files_the_row_that_is_a_forecast.py +0 -0
  208. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_plan_ranks_only_when_told_how.py +0 -0
  209. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_posterior_is_arithmetic_not_assumption.py +0 -0
  210. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_posterior_says_which_reading_it_left_out.py +0 -0
  211. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_prediction_ledger_outlives_the_process.py +0 -0
  212. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_projection_declines_rather_than_guessing.py +0 -0
  213. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_proposal_says_what_adopting_it_would_have_caught.py +0 -0
  214. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_proposed_spread_says_what_it_assumed.py +0 -0
  215. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_ranking_says_how_close_the_call_was.py +0 -0
  216. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_reading_can_be_impossible_without_being_extreme.py +0 -0
  217. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_refused_block_says_which_rule_it_was_on.py +0 -0
  218. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_refused_coupling_is_a_question_not_a_silence.py +0 -0
  219. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_response_model_nobody_recognises_is_not_exponential.py +0 -0
  220. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_rollout_can_be_graded_later.py +0 -0
  221. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_rollout_carries_a_transient_past_the_action_step.py +0 -0
  222. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_rollout_steps_the_model_forward.py +0 -0
  223. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_second_action_on_a_moved_property_superposes.py +0 -0
  224. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_sentence_the_changelog_closed_is_gone.py +0 -0
  225. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_set_aside_forecast_says_who_set_it_aside.py +0 -0
  226. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_source_pinned_at_zero_passes_no_doubt_on.py +0 -0
  227. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_spread_nobody_declared_is_not_a_declared_zero.py +0 -0
  228. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_starved_join_says_which_operand_starved.py +0 -0
  229. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_surprise_the_store_never_saw_is_not_a_miss.py +0 -0
  230. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_tie_break_knows_which_side_of_the_line.py +0 -0
  231. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_tie_is_broken_by_the_declared_margin.py +0 -0
  232. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_transition_into_an_acted_property_still_arrives.py +0 -0
  233. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_two_stage_chain_composes_exactly.py +0 -0
  234. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_value_is_final_before_anything_reads_it.py +0 -0
  235. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_vertical_may_adopt_and_this_engine_may_not.py +0 -0
  236. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_warning_is_a_finding_on_every_surface.py +0 -0
  237. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_what_if_is_evaluated_at_the_horizon_it_asked_for.py +0 -0
  238. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_window_cannot_see_past_the_frozen_clock.py +0 -0
  239. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_withdrawn_check_is_not_silent.py +0 -0
  240. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_a_zero_allowance_still_reports.py +0 -0
  241. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_absent_data_regressions.py +0 -0
  242. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_agreement_declines_what_it_used_to_guess.py +0 -0
  243. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_an_action_may_arrive_as_a_mapping.py +0 -0
  244. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_an_action_on_a_coupled_property_reads_the_value_it_has.py +0 -0
  245. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_an_action_outlives_the_forecast_it_overrides.py +0 -0
  246. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_an_action_that_never_runs_says_so.py +0 -0
  247. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_an_imagined_breach_is_not_a_live_one_in_the_telemetry.py +0 -0
  248. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_an_imagined_finding_is_not_a_live_one.py +0 -0
  249. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_an_invented_time_course_says_so.py +0 -0
  250. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_an_undeclared_transition_projects_nothing.py +0 -0
  251. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_closed_reader.py +0 -0
  252. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_connectivity_required_property.py +0 -0
  253. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_conservation_absent_output.py +0 -0
  254. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_cross_signal_consistency.py +0 -0
  255. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_decline_contract.py +0 -0
  256. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_did_you_mean_is_case_insensitive.py +0 -0
  257. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_discovery_proposes_and_never_promotes.py +0 -0
  258. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_documented_paths_the_suite_never_ran.py +0 -0
  259. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_entailment_derives_and_refuses.py +0 -0
  260. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_envelope_schema.py +0 -0
  261. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_every_assumption_stamp_has_one_name.py +0 -0
  262. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_every_declared_tool_has_a_wrapper.py +0 -0
  263. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_every_decline_reason_has_a_producer.py +0 -0
  264. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_every_discipline_decline_has_one_name.py +0 -0
  265. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_every_forecast_gets_a_yardstick.py +0 -0
  266. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_every_reader_takes_the_same_history.py +0 -0
  267. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_every_shipped_example_actually_runs.py +0 -0
  268. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_every_simulation_decline_has_an_input.py +0 -0
  269. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_gated_cell_declines.py +0 -0
  270. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_history_outlives_the_process_and_the_weekend.py +0 -0
  271. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_homeostasis_setpoint.py +0 -0
  272. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_importing_the_package_does_not_pull_an_optional_extra.py +0 -0
  273. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_lower_bounds.py +0 -0
  274. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_mcp_feeders.py +0 -0
  275. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_mcp_roundtrip.py +0 -0
  276. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_monotonicity_reversals.py +0 -0
  277. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_no_axiom_retires_a_check.py +0 -0
  278. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_no_cell_is_judged_twice.py +0 -0
  279. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_no_identifier_names_a_private_record.py +0 -0
  280. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_no_test_anchors_time_at_import.py +0 -0
  281. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_one_declared_spread_is_counted_once.py +0 -0
  282. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_one_question_gets_one_projection.py +0 -0
  283. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_one_set_of_names_the_model_reads.py +0 -0
  284. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_role_comes_from_the_model.py +0 -0
  285. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_slow_oscillation.py +0 -0
  286. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_alpha_figure_comes_from_the_record.py +0 -0
  287. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_axioms_run_over_the_forecast_itself.py +0 -0
  288. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_causal_verb_has_a_model_to_run_against.py +0 -0
  289. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_clock_can_be_moved.py +0 -0
  290. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_config_blocks_are_documented.py +0 -0
  291. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_documented_priorities_are_the_computed_ones.py +0 -0
  292. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_engine_declares_no_domain_it_does_not_serve.py +0 -0
  293. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_engine_invents_no_indicators.py +0 -0
  294. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_engine_races_its_own_forecasts.py +0 -0
  295. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_engine_says_what_its_approximation_cost.py +0 -0
  296. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_engine_scores_its_own_declared_spread.py +0 -0
  297. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_engine_ships_an_opponent_worth_beating.py +0 -0
  298. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_engines_own_forecast_is_not_a_producers.py +0 -0
  299. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_envelope_legs_are_documented.py +0 -0
  300. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_evidence_floor_is_declared_or_disclosed.py +0 -0
  301. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_forecasts_leg_says_how_many_were_expected.py +0 -0
  302. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_indicator_keys_are_documented.py +0 -0
  303. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_ingest_tag_names_the_caller_not_the_funnel.py +0 -0
  304. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_lane_cleans_up_after_itself.py +0 -0
  305. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_ledger_is_reachable_without_a_deep_import.py +0 -0
  306. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_legal_files_ship_together.py +0 -0
  307. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_namespaces_point_at_a_graph_that_ships.py +0 -0
  308. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_policy_document_and_the_changelog_agree.py +0 -0
  309. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_published_cascade_formula_is_the_one_that_runs.py +0 -0
  310. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_question_ranking_is_one_scale.py +0 -0
  311. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_rate_arm_declines_what_it_used_to_guess.py +0 -0
  312. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_readme_decline_count_is_derived.py +0 -0
  313. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_readme_name_count_is_derived.py +0 -0
  314. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_readme_names_the_forms_load_model_takes.py +0 -0
  315. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_rename_table_names_real_things.py +0 -0
  316. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_sample_floor_says_whether_it_is_reachable.py +0 -0
  317. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_schema_names_every_sub_envelope.py +0 -0
  318. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_schema_names_what_a_sub_envelope_carries.py +0 -0
  319. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_session_keeps_its_own_ledger.py +0 -0
  320. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_stance_page_says_what_no_other_page_says.py +0 -0
  321. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_verb_that_used_the_number_names_it.py +0 -0
  322. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_the_world_model_can_be_exercised_over_mcp.py +0 -0
  323. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_threshold_overrides.py +0 -0
  324. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_timestamped_ingestion.py +0 -0
  325. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_traverse_vocabularies.py +0 -0
  326. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_trend_widens_and_does_not_point.py +0 -0
  327. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_two_actions_at_two_instants_are_not_one.py +0 -0
  328. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_two_bare_series_are_joinable.py +0 -0
  329. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_two_claims_on_one_edge_are_two_questions.py +0 -0
  330. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_two_offset_couplings_from_one_source_are_both_charged.py +0 -0
  331. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_two_plans_that_cost_the_same_compare_equal.py +0 -0
  332. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_two_rules_can_close_a_loop_neither_closes_alone.py +0 -0
  333. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_two_settings_of_one_property_do_not_add.py +0 -0
  334. {arbiter_engine-0.2.8 → arbiter_engine-0.2.9}/tests/test_unread_properties.py +0 -0
@@ -34,6 +34,26 @@ useful-looking document and the less trustworthy one.
34
34
 
35
35
  ## [Unreleased]
36
36
 
37
+ ## [0.2.9] — 2026-09-25
38
+
39
+ ### Fixed
40
+
41
+ - **`model_describe` and `gaps` work over every history this package ships.**
42
+ Both asked the session's history for `series_keys()`, which only the
43
+ in-memory store implemented, so a session built over
44
+ `SqliteObservationHistory` or `CalendarHistory` raised `AttributeError`, and
45
+ `rollout` ran with no seeded past (`precondition_unmet`). Both stores now list
46
+ their series. A history of the caller's own that implements only the five
47
+ `ObservationHistory` methods gets `unconsumed_observations: None` -- the report
48
+ could not be made -- rather than a crash or an empty list.
49
+ - **A forecast is graded through whatever history the session reads.** The
50
+ ledger read the in-memory store's private series and skipped any other, so
51
+ a forecast made over `SqliteObservationHistory`, in a model declaring a
52
+ `calendar:`, or in a model with any derived indicator matured `ungradeable`
53
+ every time: measured, none of twelve graded in each case, against twelve of
54
+ twelve on the plain store. It reads through `get_values` now, and all four
55
+ grade alike. No calibration figure moves for a session that graded before.
56
+
37
57
  ## [0.2.8] — 2026-09-25
38
58
 
39
59
  ### Added
@@ -9,7 +9,7 @@ authors:
9
9
  alias: "james-sheen"
10
10
  - name: "The Arbiter Project Authors"
11
11
  license: Apache-2.0
12
- version: "0.2.8"
12
+ version: "0.2.9"
13
13
  date-released: "2026-09-25"
14
14
  repository-code: "https://github.com/james-sheen/arbiter"
15
15
  url: "https://github.com/james-sheen/arbiter"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arbiter-engine
3
- Version: 0.2.8
3
+ Version: 0.2.9
4
4
  Summary: A detection engine that reports what it did not check.
5
5
  Project-URL: Homepage, https://github.com/james-sheen/arbiter
6
6
  Project-URL: Source, https://github.com/james-sheen/arbiter
@@ -536,7 +536,7 @@ class EngineSession:
536
536
  })
537
537
  return records
538
538
 
539
- def unconsumed_observations(self) -> List[Dict[str, Any]]:
539
+ def unconsumed_observations(self) -> Optional[List[Dict[str, Any]]]:
540
540
  """Series this session holds that no declared indicator will ever read.
541
541
 
542
542
  This rules the second half of issue #1. ``add_observations``
@@ -564,9 +564,18 @@ class EngineSession:
564
564
  # report on the other input surface counted them as read. One
565
565
  # declaration, two verdicts, and the only way to silence the wrong one
566
566
  # was to declare the property a second time.
567
+ # A HISTORY THAT CANNOT LIST ITS SERIES CANNOT BE ASKED
568
+ # THIS, and the answer says so rather than crashing or claiming the
569
+ # empty list. `series_keys` is not one of the ABC's five methods; both
570
+ # stores this engine ships implement it, and a caller's own need not.
571
+ # `None` is the engine's convention for *not measured*, and every
572
+ # consumer in the family already reads this key through `or []`.
573
+ keys = getattr(self.history, "series_keys", None)
574
+ if keys is None:
575
+ return None
567
576
  declared: Dict[str, set] = self.readable_properties()
568
577
  records: List[Dict[str, Any]] = []
569
- for entity_id, prop in self.history.series_keys():
578
+ for entity_id, prop in keys():
570
579
  entity = self.entities.get(entity_id)
571
580
  if entity is None:
572
581
  reason = "unknown_entity"
@@ -226,3 +226,8 @@ class CalendarHistory(ObservationHistory):
226
226
 
227
227
  def get_observation_count(self, entity_id: str, property_name: str) -> int:
228
228
  return self.inner.get_observation_count(entity_id, property_name)
229
+
230
+ def series_keys(self):
231
+ """The series the history underneath holds -- a calendar changes which
232
+ instants count, never which series exist."""
233
+ return self.inner.series_keys()
@@ -135,6 +135,24 @@ class SqliteObservationHistory(ObservationHistory):
135
135
  return [(_from_seconds(ts), text if text is not None else str(number))
136
136
  for ts, number, text in rows]
137
137
 
138
+ def series_keys(self) -> List[Tuple[str, str]]:
139
+ """Every ``(entity_id, property_name)`` this store holds a series for.
140
+
141
+ Not one of the ABC's five, and the in-memory store had it
142
+ from on -- so every reader that enumerates a history was
143
+ written against the default and this class was never asked. Two of
144
+ them crashed on it: `model_describe` and `gaps` raised AttributeError
145
+ for any session built over this store, and `rollout` ran with no
146
+ imagined past behind it, declining `precondition_unmet`. Nothing
147
+ downstream had constructed one until a vertical kept its readings
148
+ across runs, which is the one use this class exists for.
149
+ """
150
+ with self._lock:
151
+ rows = self._connection.execute(
152
+ "SELECT DISTINCT entity_id, property FROM obs"
153
+ " ORDER BY entity_id, property").fetchall()
154
+ return [(str(entity_id), str(prop)) for entity_id, prop in rows]
155
+
138
156
  def get_observations(self, entity_id: str, start: datetime,
139
157
  end: datetime) -> List[Observation]:
140
158
  with self._lock:
@@ -48,7 +48,7 @@ import uuid
48
48
  from collections import deque
49
49
  from dataclasses import dataclass, field
50
50
  from datetime import datetime, timedelta
51
- from ..clock import as_naive_utc, now_utc
51
+ from ..clock import as_naive_utc, as_of, now_utc
52
52
  # the id the yardstick is filed under, read from the module that
53
53
  # owns it rather than re-spelled here. Two spellings of one literal is how
54
54
  # the exclusion below would silently stop excluding anything.
@@ -698,18 +698,47 @@ class PredictionLedger:
698
698
  property_name: str,
699
699
  start: datetime,
700
700
  end: datetime,
701
+ now: Optional[datetime] = None,
701
702
  ) -> List[Tuple[datetime, float]]:
702
- """Numeric observations for (entity, property) in [start, end],
703
- read defensively from the in-memory histories."""
703
+ """Numeric observations for (entity, property) in [start, end], read
704
+ through each history's own `get_values` -- never its internals.
705
+
706
+ This read the in-memory store's private `_history` dict and
707
+ SKIPPED any history without one. `check` hands the ledger the session's
708
+ READING history, which is the raw store only when the model declares
709
+ neither a calendar nor a derived indicator; otherwise it is a view, and
710
+ so is the durable store. Measured, the same twenty minutes of one tank
711
+ filing a forecast a minute: the in-memory store graded 12 of 12; the
712
+ durable store, a declared calendar and a model with one derived
713
+ indicator graded NONE, all twelve `ungradeable`. A calibration figure
714
+ that could never be anything but empty, reported without an error --
715
+ beside a comment in `check` saying the view was passed so that a
716
+ derived forecast WOULD have something to score against.
717
+
718
+ `get_values` is the one reader every history implements, and it ends
719
+ at the engine's clock -- so the read is pinned to the GRADING instant,
720
+ `now`, which a caller may pass explicitly and which the private dict
721
+ never consulted. The window reaches back to `start`, a microsecond
722
+ wider because the stores bound it exclusively, and is then cut to
723
+ exactly `[start, end]`. A calendar view answers a window in OPEN time,
724
+ which reaches further back in wall time, and the cut is what makes
725
+ that harmless.
726
+ """
704
727
  out: List[Tuple[datetime, float]] = []
705
- for history in histories or []:
706
- raw = getattr(history, "_history", None)
707
- if not isinstance(raw, dict):
708
- continue
709
- for ts, value in raw.get((entity_id, property_name)) or []:
710
- if (isinstance(ts, datetime) and isinstance(value, (int, float))
711
- and start <= ts <= end):
712
- out.append((ts, float(value)))
728
+ at = as_naive_utc(now) if now is not None else now_utc()
729
+ span = at - start + timedelta(microseconds=1)
730
+ if span.total_seconds() <= 0:
731
+ return out
732
+ with as_of(at):
733
+ for history in histories or []:
734
+ reader = getattr(history, "get_values", None)
735
+ if reader is None:
736
+ continue
737
+ for ts, value in reader(entity_id, property_name, span) or []:
738
+ if (isinstance(ts, datetime)
739
+ and isinstance(value, (int, float))
740
+ and start <= ts <= end):
741
+ out.append((ts, float(value)))
713
742
  return out
714
743
 
715
744
  def _grade_distribution_record(
@@ -743,7 +772,7 @@ class PredictionLedger:
743
772
  return None
744
773
  observations = self._observations_for(
745
774
  list(histories), record.entity_id, record.indicator or "",
746
- record.predicted_at, window_end)
775
+ record.predicted_at, window_end, now)
747
776
  if not observations:
748
777
  record.verdict = GRADE_UNGRADEABLE
749
778
  record.graded_at = now
@@ -779,7 +808,7 @@ class PredictionLedger:
779
808
  return None
780
809
  observations = self._observations_for(
781
810
  list(histories), record.entity_id, record.indicator or "",
782
- record.predicted_at, window_end)
811
+ record.predicted_at, window_end, now)
783
812
  target = record.predicted_at + timedelta(seconds=record.horizon_s)
784
813
  # AND EACH READING GRADES ONE HORIZON, not every horizon
785
814
  # whose window contains it. This took the observation closest to the
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arbiter-engine"
7
- version = "0.2.8"
7
+ version = "0.2.9"
8
8
  description = "A detection engine that reports what it did not check."
9
9
  requires-python = ">=3.10"
10
10
  license = "Apache-2.0"
@@ -0,0 +1,260 @@
1
+ """Every history this engine ships can be described, gapped and rolled from.
2
+
3
+ `SqliteObservationHistory` and `CalendarHistory` have been supported names since
4
+ 0.2.3. `model_describe` and `gaps` both asked the session's history for
5
+ `series_keys()` -- a method the in-memory store has and the ABC does not
6
+ require -- and so both RAISED AttributeError for any session built over either
7
+ of the other two. `rollout` checked for the method and, finding none, ran with
8
+ no imagined past, declining `precondition_unmet`.
9
+
10
+ NOTHING DOWNSTREAM HAD EVER CONSTRUCTED ONE, which is how it survived. The first
11
+ caller was a vertical keeping its readings across separate runs -- the one use
12
+ the durable store exists for -- and its first `detect` died in the describe
13
+ call. Every test of the durable store here fed it and read it back through
14
+ `get_values`, which is the half that worked.
15
+
16
+ So this is asked of EVERY history the engine ships, through the verbs a caller
17
+ actually uses, rather than of one method on one class.
18
+ """
19
+
20
+ from __future__ import annotations
21
+
22
+ import pathlib
23
+ from datetime import datetime, timedelta
24
+
25
+ import pytest
26
+
27
+ from arbiter_engine import api
28
+ from arbiter_engine.history.calendar import (CalendarHistory,
29
+ SessionCalendar)
30
+ from arbiter_engine.history.observation import \
31
+ InMemoryObservationHistory
32
+ from arbiter_engine.history.sqlite_store import \
33
+ SqliteObservationHistory
34
+ from arbiter_engine.interfaces import ObservationHistory
35
+
36
+ MODEL = """
37
+ domain:
38
+ id: histories
39
+ name: every shipped history
40
+ entity_types: [Tank]
41
+ indicators:
42
+ Tank:
43
+ - name: level
44
+ type: NUMERIC
45
+ axioms: [BOUNDEDNESS]
46
+ warning: 80
47
+ critical: 90
48
+ """
49
+
50
+ AT = datetime(2026, 9, 25, 12, 0)
51
+
52
+
53
+ def _always_open():
54
+ """A calendar with no sessions, which counts every instant -- the history
55
+ underneath is what is under test, not the calendar."""
56
+ return SessionCalendar()
57
+
58
+
59
+ HISTORIES = {
60
+ "in_memory": lambda: InMemoryObservationHistory(),
61
+ "sqlite": lambda: SqliteObservationHistory(":memory:"),
62
+ "calendar_over_memory": lambda: CalendarHistory(
63
+ InMemoryObservationHistory(), _always_open()),
64
+ "calendar_over_sqlite": lambda: CalendarHistory(
65
+ SqliteObservationHistory(":memory:"), _always_open()),
66
+ }
67
+
68
+
69
+ def _session(history, tmp_path):
70
+ path = tmp_path / "model.yaml"
71
+ path.write_text(MODEL)
72
+ session = api.EngineSession(history=history)
73
+ session.load_model(str(path))
74
+ session.add_entity("tank-1", "Tank", {"level": 50.0})
75
+ with api.as_of(AT):
76
+ session.add_observations("tank-1", "level",
77
+ [(AT - timedelta(minutes=5 - i), 50.0 + i)
78
+ for i in range(5)])
79
+ # A series no indicator reads -- the report this breaks exists for it.
80
+ session.add_observations("tank-1", "levle",
81
+ [(AT - timedelta(minutes=1), 1.0)])
82
+ return session
83
+
84
+
85
+ @pytest.fixture(params=sorted(HISTORIES))
86
+ def shipped(request, tmp_path):
87
+ return request.param, _session(HISTORIES[request.param](), tmp_path)
88
+
89
+
90
+ class TestTheVerbsThatEnumerateAHistory:
91
+
92
+ def test_model_describe_answers(self, shipped):
93
+ name, session = shipped
94
+ with api.as_of(AT):
95
+ payload = api.model_describe(session).to_dict()
96
+ assert "unconsumed_observations" in payload, name
97
+
98
+ def test_gaps_answers(self, shipped):
99
+ name, session = shipped
100
+ with api.as_of(AT):
101
+ api.check(session)
102
+ api.gaps(session).to_dict()
103
+
104
+ def test_the_unread_series_is_reported_the_same_way_by_all_of_them(
105
+ self, shipped):
106
+ """Same feed, same report, whichever store holds it -- the store is a
107
+ durability choice and must not change what the engine says."""
108
+ name, session = shipped
109
+ records = session.unconsumed_observations()
110
+ assert [(r["entity_id"], r["property"], r["reason"]) for r in records] == [
111
+ ("tank-1", "levle", "undeclared_property")], name
112
+
113
+ def test_a_rollout_is_seeded_from_it(self, shipped):
114
+ """`precondition_unmet` is what a rollout says when it could not read
115
+ the past; on a store the engine ships, it must never say it."""
116
+ name, session = shipped
117
+ with api.as_of(AT):
118
+ payload = api.rollout(session, horizon_s=120.0,
119
+ step_s=60.0).to_dict()["simulation"]
120
+ reasons = {d["reason"] for d in payload.get("not_checked", [])}
121
+ assert "precondition_unmet" not in reasons, (name, reasons)
122
+
123
+
124
+ class TestTheDurableStoreListsItsSeries:
125
+
126
+ def test_distinct_and_ordered(self):
127
+ store = SqliteObservationHistory(":memory:")
128
+ for entity, prop in (("b", "x"), ("a", "y"), ("a", "x"), ("a", "x")):
129
+ store.add(entity, prop, 1.0, AT)
130
+ assert store.series_keys() == [("a", "x"), ("a", "y"), ("b", "x")]
131
+
132
+ def test_it_survives_the_process(self, tmp_path):
133
+ path = str(tmp_path / "h.sqlite")
134
+ SqliteObservationHistory(path).add("tank-1", "level", 1.0, AT)
135
+ assert SqliteObservationHistory(path).series_keys() == [("tank-1", "level")]
136
+
137
+
138
+ class _OnlyTheAbc(ObservationHistory):
139
+ """A caller's own history, implementing exactly the five methods required."""
140
+
141
+ def __init__(self):
142
+ self._inner = InMemoryObservationHistory()
143
+
144
+ def add(self, *args, **kwargs):
145
+ return self._inner.add(*args, **kwargs)
146
+
147
+ def get_values(self, *args, **kwargs):
148
+ return self._inner.get_values(*args, **kwargs)
149
+
150
+ def get_states(self, *args, **kwargs):
151
+ return self._inner.get_states(*args, **kwargs)
152
+
153
+ def get_observations(self, *args, **kwargs):
154
+ return self._inner.get_observations(*args, **kwargs)
155
+
156
+ def get_observation_count(self, *args, **kwargs):
157
+ return self._inner.get_observation_count(*args, **kwargs)
158
+
159
+
160
+ class TestAHistoryThatCannotListItsSeriesSaysSo:
161
+ """The ABC does not require `series_keys`, so a caller's history may lack it.
162
+ The report it feeds cannot be made then -- and `[]` would say it was made
163
+ and found nothing."""
164
+
165
+ def test_the_report_is_none_not_empty(self, tmp_path):
166
+ session = _session(_OnlyTheAbc(), tmp_path)
167
+ assert session.unconsumed_observations() is None
168
+
169
+ def test_describe_still_answers(self, tmp_path):
170
+ session = _session(_OnlyTheAbc(), tmp_path)
171
+ with api.as_of(AT):
172
+ payload = api.model_describe(session).to_dict()
173
+ assert payload["unconsumed_observations"] is None
174
+
175
+
176
+ FORECAST_MODEL = """
177
+ domain:
178
+ id: graded
179
+ name: a forecast graded through every view
180
+ entity_types: [Tank]
181
+ indicators:
182
+ Tank:
183
+ - name: level
184
+ type: NUMERIC
185
+ axioms: [BOUNDEDNESS]
186
+ warning: 1000
187
+ critical: 2000
188
+ window: 1h
189
+ dynamics: {model: random_walk}
190
+ """
191
+
192
+ #: A calendar open around the clock is still a declared calendar, so the view
193
+ #: `check` hands the ledger is the calendar wrapper -- which is the point.
194
+ CALENDAR = (" calendar:\n sessions:\n - {days: [mon, tue, wed, thu, fri, "
195
+ "sat, sun], open: '00:00', close: '23:59'}\n")
196
+
197
+ #: One derived indicator anywhere in the model wraps the whole reading history.
198
+ DERIVED = (" - name: double\n type: NUMERIC\n axioms: [BOUNDEDNESS]\n"
199
+ " warning: 1000\n critical: 2000\n"
200
+ " derived: {expression: \"level + level\"}\n")
201
+
202
+ VIEWS = {
203
+ "in_memory": (FORECAST_MODEL, InMemoryObservationHistory),
204
+ "sqlite": (FORECAST_MODEL, lambda: SqliteObservationHistory(":memory:")),
205
+ "calendar": (FORECAST_MODEL.replace(" entity_types:", CALENDAR + " entity_types:"),
206
+ InMemoryObservationHistory),
207
+ "derived": (FORECAST_MODEL + DERIVED, InMemoryObservationHistory),
208
+ }
209
+
210
+
211
+ def _graded(model_text, history, tmp_path):
212
+ """Twenty minutes of one tank, a forecast filed each minute from the
213
+ seventh, one fresh session per minute over the same two stores."""
214
+ import random
215
+
216
+ tmp_path.mkdir(parents=True, exist_ok=True)
217
+ path = tmp_path / "m.yaml"
218
+ path.write_text(model_text)
219
+ rng, level, ledger = random.Random(1), 100.0, None
220
+ for minute in range(20):
221
+ at = AT + timedelta(minutes=minute)
222
+ level += rng.gauss(0.0, 1.0)
223
+ with api.as_of(at):
224
+ session = api.EngineSession(history=history, ledger=ledger)
225
+ session.load_model(str(path))
226
+ session.add_entity("t1", "Tank", {"level": level})
227
+ session.add_observations("t1", "level", [(at, level)])
228
+ ledger = session.ledger
229
+ api.check(session)
230
+ if minute >= 6:
231
+ api.project(session, horizon_s=60.0)
232
+ return ledger.calibration(), type(session.reading_history()).__name__
233
+
234
+
235
+ class TestAForecastIsGradedThroughEveryView:
236
+ """The ledger read the in-memory store's private dict and skipped anything
237
+ else, so the durable store, a declared calendar and one derived indicator
238
+ each graded NONE of twelve forecasts -- every one `ungradeable`."""
239
+
240
+ @pytest.mark.parametrize("view", sorted(VIEWS))
241
+ def test_it_grades_what_the_plain_store_grades(self, view, tmp_path):
242
+ text, make = VIEWS[view]
243
+ cal, name = _graded(text, make(), tmp_path / view)
244
+ plain_text, plain_store = VIEWS["in_memory"]
245
+ control, _ = _graded(plain_text, plain_store(), tmp_path / "control")
246
+ assert control["confirmed"] + control["falsified"] > 0, (
247
+ "the control graded nothing, so parity with it would be free")
248
+ assert cal["ungradeable"] == 0, (view, name, cal["ungradeable"])
249
+ assert (cal["confirmed"], cal["falsified"]) == (
250
+ control["confirmed"], control["falsified"]), (view, name)
251
+
252
+ def test_the_views_are_really_different_objects(self, tmp_path):
253
+ """Non-vacuity: if every case were the plain store underneath, the
254
+ parity above would be free."""
255
+ names = {view: _graded(text, make(), tmp_path / view)[1]
256
+ for view, (text, make) in VIEWS.items()}
257
+ assert names == {"in_memory": "InMemoryObservationHistory",
258
+ "sqlite": "SqliteObservationHistory",
259
+ "calendar": "CalendarHistory",
260
+ "derived": "DerivedHistoryView"}, names
File without changes
File without changes
File without changes
File without changes