arbiter-engine 0.2.7__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 (335) hide show
  1. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/.github/workflows/release.yml +26 -14
  2. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/CHANGELOG.md +59 -0
  3. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/CITATION.cff +1 -1
  4. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/MODELING.md +15 -2
  5. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/PKG-INFO +1 -1
  6. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/api.py +36 -3
  7. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/assumptions.py +19 -1
  8. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/examples/water_tank.yaml +1 -1
  9. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/history/calendar.py +5 -0
  10. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/history/sqlite_store.py +18 -0
  11. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/inference/hypothesis.py +23 -2
  12. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/inference/runner.py +79 -8
  13. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/residual/predict_vs_mirror.py +42 -13
  14. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/scripts/benchmark_check.py +1 -1
  15. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/traverser.py +1 -1
  16. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/evidence/alpha1_evidence_pack.md +2 -2
  17. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/evidence/l5_surprise_synthesis.md +3 -3
  18. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/examples/water_tank.yaml +1 -1
  19. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/pyproject.toml +1 -1
  20. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecaster_is_judged_by_the_same_eight_axioms.py +1 -1
  21. arbiter_engine-0.2.9/tests/test_a_posterior_says_which_reading_it_left_out.py +207 -0
  22. arbiter_engine-0.2.9/tests/test_every_history_the_engine_ships_can_be_described.py +260 -0
  23. arbiter_engine-0.2.9/tests/test_the_alpha_figure_comes_from_the_record.py +108 -0
  24. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_evidence_floor_is_declared_or_disclosed.py +23 -7
  25. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_stance_page_says_what_no_other_page_says.py +14 -51
  26. arbiter_engine-0.2.7/.github/PUBLISH_FROM_CI +0 -43
  27. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/.gitattributes +0 -0
  28. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  29. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  30. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/.github/workflows/tests.yml +0 -0
  31. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/.github/workflows/verify-published.yml +0 -0
  32. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/.github/workflows/verify-tag-artifact.yml +0 -0
  33. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/.gitignore +0 -0
  34. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/AI_ATTRIBUTION.md +0 -0
  35. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/AUTHORS.md +0 -0
  36. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/BRIDGES.md +0 -0
  37. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/CODE_OF_CONDUCT.md +0 -0
  38. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/COMPATIBILITY.md +0 -0
  39. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/CONTRIBUTING.md +0 -0
  40. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/LICENSE +0 -0
  41. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/NOTICE +0 -0
  42. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/PRIVACY.md +0 -0
  43. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/README.md +0 -0
  44. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/ROADMAP.md +0 -0
  45. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/SECURITY.md +0 -0
  46. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/STANCE.md +0 -0
  47. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/SUPPORT.md +0 -0
  48. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/TRADEMARK.md +0 -0
  49. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/__init__.py +0 -0
  50. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/axiom_thresholds.py +0 -0
  51. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/causal/__init__.py +0 -0
  52. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/causal/discovery.py +0 -0
  53. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/causal/granger.py +0 -0
  54. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/causal/leadlag.py +0 -0
  55. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/clock.py +0 -0
  56. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/derived/__init__.py +0 -0
  57. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/derived/indicator.py +0 -0
  58. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/derived/parser.py +0 -0
  59. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/envelope.py +0 -0
  60. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/examples/battery_pack.yaml +0 -0
  61. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/examples/factory_line.yaml +0 -0
  62. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/examples/kubernetes_node.yaml +0 -0
  63. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/examples/margin_book.yaml +0 -0
  64. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/examples/pump_tank_dynamics.yaml +0 -0
  65. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/examples/pump_tank_planning.yaml +0 -0
  66. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/examples/substation_feeder.yaml +0 -0
  67. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/examples/substation_feeder_surprises.yaml +0 -0
  68. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/fire_frequency.py +0 -0
  69. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/forecast/__init__.py +0 -0
  70. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/forecast/contract.py +0 -0
  71. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/forecast/envelope.py +0 -0
  72. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/forecast/ingest.py +0 -0
  73. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/forecast/monitor.py +0 -0
  74. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/forecast/shadow.py +0 -0
  75. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/history/__init__.py +0 -0
  76. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/history/observation.py +0 -0
  77. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/history/observation_production.py +0 -0
  78. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/history/observation_source_wiring.py +0 -0
  79. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/history/readiness.py +0 -0
  80. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/inference/__init__.py +0 -0
  81. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/inference/causal.py +0 -0
  82. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/inference/ve.py +0 -0
  83. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/interfaces.py +0 -0
  84. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/mcp/__init__.py +0 -0
  85. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/mcp/server.py +0 -0
  86. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/__init__.py +0 -0
  87. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axiom_verdicts_production.py +0 -0
  88. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/__init__.py +0 -0
  89. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/boundedness.py +0 -0
  90. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/connectivity.py +0 -0
  91. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/conservation.py +0 -0
  92. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/consistency.py +0 -0
  93. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/extensions.py +0 -0
  94. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/homeostasis.py +0 -0
  95. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/monotonicity.py +0 -0
  96. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/peers.py +0 -0
  97. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/responsiveness.py +0 -0
  98. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/roles.py +0 -0
  99. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/axioms/stability.py +0 -0
  100. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/domain_loader.py +0 -0
  101. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/entail.py +0 -0
  102. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/loader.py +0 -0
  103. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/prediction_production.py +0 -0
  104. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/reasoner.py +0 -0
  105. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/ontology/schemas/health_meta_ontology.ttl +0 -0
  106. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/producers/__init__.py +0 -0
  107. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/producers/baseline_learner.py +0 -0
  108. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/projection/__init__.py +0 -0
  109. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/projection/projector.py +0 -0
  110. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/projection/runner.py +0 -0
  111. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/propagation/__init__.py +0 -0
  112. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/propagation/impact_estimator.py +0 -0
  113. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/propagation/lp_confidence.py +0 -0
  114. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/propagation/mcts_root_cause.py +0 -0
  115. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/propagation/root_cause.py +0 -0
  116. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/propagation/weight_learner.py +0 -0
  117. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/rca/__init__.py +0 -0
  118. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/rca/greedy_set_cover.py +0 -0
  119. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/replay.py +0 -0
  120. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/residual/__init__.py +0 -0
  121. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/residual/sqlite_ledger.py +0 -0
  122. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/schema/envelope.schema.json +0 -0
  123. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/scripts/__init__.py +0 -0
  124. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/scripts/surprise_benchmark.py +0 -0
  125. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/subenvelope.py +0 -0
  126. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/surprises.py +0 -0
  127. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/temporal/__init__.py +0 -0
  128. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/temporal/temporal_edge.py +0 -0
  129. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/temporal/trend_projection.py +0 -0
  130. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/__init__.py +0 -0
  131. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/action_clears_problem.py +0 -0
  132. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/actions.py +0 -0
  133. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/builder.py +0 -0
  134. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/gap.py +0 -0
  135. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/hypothesis_generator.py +0 -0
  136. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/hypothesis_production.py +0 -0
  137. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/kernel_pipeline_executor.py +0 -0
  138. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/monte_carlo_predictor.py +0 -0
  139. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/optimization_production.py +0 -0
  140. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/pipeline_production.py +0 -0
  141. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/planner.py +0 -0
  142. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/rollout.py +0 -0
  143. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/topology.py +0 -0
  144. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/topology_optimizer.py +0 -0
  145. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/transition_learner.py +0 -0
  146. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/twin/traverser_production.py +0 -0
  147. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/arbiter_engine/types.py +0 -0
  148. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/evidence/README.md +0 -0
  149. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/evidence/customer_deployment_runbook.md +0 -0
  150. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/evidence/extended_fault_scenarios.md +0 -0
  151. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/evidence/observability_handoff_guide.md +0 -0
  152. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/evidence/tech_brief.md +0 -0
  153. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/evidence/use-case-catalogue.md +0 -0
  154. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/examples/battery_pack.yaml +0 -0
  155. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/examples/factory_line.yaml +0 -0
  156. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/examples/kubernetes_node.yaml +0 -0
  157. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/examples/margin_book.yaml +0 -0
  158. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/examples/pump_tank_dynamics.yaml +0 -0
  159. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/examples/pump_tank_planning.yaml +0 -0
  160. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/examples/substation_feeder.yaml +0 -0
  161. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/examples/substation_feeder_surprises.yaml +0 -0
  162. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/schema/envelope.schema.json +0 -0
  163. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/conftest.py +0 -0
  164. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_balance_may_cross_an_entity_boundary.py +0 -0
  165. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_bound_can_belong_to_one_instance.py +0 -0
  166. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_calibration_figure_names_its_population.py +0 -0
  167. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_calibration_says_how_many_trajectories_it_saw.py +0 -0
  168. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_chain_of_lags_says_how_it_was_composed.py +0 -0
  169. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_changelog_section_says_whether_you_can_install_it.py +0 -0
  170. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_check_that_cannot_run_says_so.py +0 -0
  171. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_check_that_declines_is_heard.py +0 -0
  172. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_checker_asks_no_entity_type.py +0 -0
  173. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_clean_rollout_still_counts_what_it_evaluated.py +0 -0
  174. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_companion_beside_the_models_is_not_one.py +0 -0
  175. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_correct_gain_is_not_contradicted_by_a_lagged_series.py +0 -0
  176. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_coupling_block_is_checked_too.py +0 -0
  177. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_coupling_is_graded_on_its_own_projections.py +0 -0
  178. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_coupling_learns_how_its_projections_fared.py +0 -0
  179. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_declared_bad_state_is_not_silent.py +0 -0
  180. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_declared_calendar_is_actually_read.py +0 -0
  181. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_declared_edge_reaches_the_published_surface.py +0 -0
  182. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_declared_floor_is_checked_on_every_path.py +0 -0
  183. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_declared_gain_moves_the_downstream_value.py +0 -0
  184. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_declared_number_is_the_first_breach.py +0 -0
  185. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_declared_offset_waits_for_the_delay.py +0 -0
  186. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_declared_source_matches_the_guide.py +0 -0
  187. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_declared_spread_reaches_the_value.py +0 -0
  188. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_decline_states_the_number_it_computed.py +0 -0
  189. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_derived_indicator_is_computed_not_fed.py +0 -0
  190. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_discipline_reports_its_own_denominator.py +0 -0
  191. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_dropped_declaration_is_not_silent.py +0 -0
  192. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_filed_source_says_who_is_not_a_producer.py +0 -0
  193. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_finding_says_where_to_look_next.py +0 -0
  194. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecast_batch_is_filed_or_accounted_for.py +0 -0
  195. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecast_check_that_refused_says_so_out_loud.py +0 -0
  196. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecast_is_graded_at_its_horizon.py +0 -0
  197. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecast_is_measured_against_a_random_walk.py +0 -0
  198. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecast_is_scored_not_just_stored.py +0 -0
  199. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecast_keeps_its_doubt.py +0 -0
  200. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecast_keeps_its_doubt_when_its_median_stands_still.py +0 -0
  201. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecast_states_its_own_distribution.py +0 -0
  202. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_forecast_widens_and_says_what_it_assumed.py +0 -0
  203. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_freeze_is_found_inside_its_window.py +0 -0
  204. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_latency_without_a_threshold_says_so.py +0 -0
  205. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_lazy_import_did_not_move_a_name.py +0 -0
  206. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_lead_lag_p_value_means_what_it_says.py +0 -0
  207. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_learned_gain_is_a_proposal.py +0 -0
  208. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_margin_is_measured_against_every_line.py +0 -0
  209. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_nested_block_is_checked_too.py +0 -0
  210. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_pair_can_beat_the_best_single_action.py +0 -0
  211. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_per_instance_bound_reaches_every_reader.py +0 -0
  212. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_plan_files_the_row_that_is_a_forecast.py +0 -0
  213. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_plan_ranks_only_when_told_how.py +0 -0
  214. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_posterior_is_arithmetic_not_assumption.py +0 -0
  215. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_prediction_ledger_outlives_the_process.py +0 -0
  216. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_projection_declines_rather_than_guessing.py +0 -0
  217. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_proposal_says_what_adopting_it_would_have_caught.py +0 -0
  218. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_proposed_spread_says_what_it_assumed.py +0 -0
  219. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_ranking_says_how_close_the_call_was.py +0 -0
  220. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_reading_can_be_impossible_without_being_extreme.py +0 -0
  221. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_refused_block_says_which_rule_it_was_on.py +0 -0
  222. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_refused_coupling_is_a_question_not_a_silence.py +0 -0
  223. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_response_model_nobody_recognises_is_not_exponential.py +0 -0
  224. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_rollout_can_be_graded_later.py +0 -0
  225. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_rollout_carries_a_transient_past_the_action_step.py +0 -0
  226. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_rollout_steps_the_model_forward.py +0 -0
  227. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_second_action_on_a_moved_property_superposes.py +0 -0
  228. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_sentence_the_changelog_closed_is_gone.py +0 -0
  229. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_set_aside_forecast_says_who_set_it_aside.py +0 -0
  230. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_source_pinned_at_zero_passes_no_doubt_on.py +0 -0
  231. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_spread_nobody_declared_is_not_a_declared_zero.py +0 -0
  232. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_starved_join_says_which_operand_starved.py +0 -0
  233. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_surprise_the_store_never_saw_is_not_a_miss.py +0 -0
  234. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_tie_break_knows_which_side_of_the_line.py +0 -0
  235. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_tie_is_broken_by_the_declared_margin.py +0 -0
  236. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_transition_into_an_acted_property_still_arrives.py +0 -0
  237. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_two_stage_chain_composes_exactly.py +0 -0
  238. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_value_is_final_before_anything_reads_it.py +0 -0
  239. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_vertical_may_adopt_and_this_engine_may_not.py +0 -0
  240. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_warning_is_a_finding_on_every_surface.py +0 -0
  241. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_what_if_is_evaluated_at_the_horizon_it_asked_for.py +0 -0
  242. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_window_cannot_see_past_the_frozen_clock.py +0 -0
  243. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_withdrawn_check_is_not_silent.py +0 -0
  244. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_a_zero_allowance_still_reports.py +0 -0
  245. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_absent_data_regressions.py +0 -0
  246. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_agreement_declines_what_it_used_to_guess.py +0 -0
  247. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_an_action_may_arrive_as_a_mapping.py +0 -0
  248. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_an_action_on_a_coupled_property_reads_the_value_it_has.py +0 -0
  249. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_an_action_outlives_the_forecast_it_overrides.py +0 -0
  250. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_an_action_that_never_runs_says_so.py +0 -0
  251. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_an_imagined_breach_is_not_a_live_one_in_the_telemetry.py +0 -0
  252. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_an_imagined_finding_is_not_a_live_one.py +0 -0
  253. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_an_invented_time_course_says_so.py +0 -0
  254. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_an_undeclared_transition_projects_nothing.py +0 -0
  255. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_closed_reader.py +0 -0
  256. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_connectivity_required_property.py +0 -0
  257. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_conservation_absent_output.py +0 -0
  258. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_cross_signal_consistency.py +0 -0
  259. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_decline_contract.py +0 -0
  260. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_did_you_mean_is_case_insensitive.py +0 -0
  261. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_discovery_proposes_and_never_promotes.py +0 -0
  262. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_documented_paths_the_suite_never_ran.py +0 -0
  263. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_entailment_derives_and_refuses.py +0 -0
  264. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_envelope_schema.py +0 -0
  265. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_every_assumption_stamp_has_one_name.py +0 -0
  266. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_every_declared_tool_has_a_wrapper.py +0 -0
  267. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_every_decline_reason_has_a_producer.py +0 -0
  268. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_every_discipline_decline_has_one_name.py +0 -0
  269. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_every_forecast_gets_a_yardstick.py +0 -0
  270. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_every_reader_takes_the_same_history.py +0 -0
  271. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_every_shipped_example_actually_runs.py +0 -0
  272. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_every_simulation_decline_has_an_input.py +0 -0
  273. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_gated_cell_declines.py +0 -0
  274. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_history_outlives_the_process_and_the_weekend.py +0 -0
  275. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_homeostasis_setpoint.py +0 -0
  276. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_importing_the_package_does_not_pull_an_optional_extra.py +0 -0
  277. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_lower_bounds.py +0 -0
  278. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_mcp_feeders.py +0 -0
  279. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_mcp_roundtrip.py +0 -0
  280. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_monotonicity_reversals.py +0 -0
  281. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_no_axiom_retires_a_check.py +0 -0
  282. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_no_cell_is_judged_twice.py +0 -0
  283. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_no_identifier_names_a_private_record.py +0 -0
  284. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_no_test_anchors_time_at_import.py +0 -0
  285. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_one_declared_spread_is_counted_once.py +0 -0
  286. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_one_question_gets_one_projection.py +0 -0
  287. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_one_set_of_names_the_model_reads.py +0 -0
  288. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_role_comes_from_the_model.py +0 -0
  289. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_slow_oscillation.py +0 -0
  290. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_axioms_run_over_the_forecast_itself.py +0 -0
  291. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_causal_verb_has_a_model_to_run_against.py +0 -0
  292. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_clock_can_be_moved.py +0 -0
  293. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_config_blocks_are_documented.py +0 -0
  294. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_documented_priorities_are_the_computed_ones.py +0 -0
  295. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_engine_declares_no_domain_it_does_not_serve.py +0 -0
  296. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_engine_invents_no_indicators.py +0 -0
  297. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_engine_races_its_own_forecasts.py +0 -0
  298. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_engine_says_what_its_approximation_cost.py +0 -0
  299. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_engine_scores_its_own_declared_spread.py +0 -0
  300. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_engine_ships_an_opponent_worth_beating.py +0 -0
  301. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_engines_own_forecast_is_not_a_producers.py +0 -0
  302. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_envelope_legs_are_documented.py +0 -0
  303. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_forecasts_leg_says_how_many_were_expected.py +0 -0
  304. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_indicator_keys_are_documented.py +0 -0
  305. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_ingest_tag_names_the_caller_not_the_funnel.py +0 -0
  306. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_lane_cleans_up_after_itself.py +0 -0
  307. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_ledger_is_reachable_without_a_deep_import.py +0 -0
  308. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_legal_files_ship_together.py +0 -0
  309. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_namespaces_point_at_a_graph_that_ships.py +0 -0
  310. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_policy_document_and_the_changelog_agree.py +0 -0
  311. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_published_cascade_formula_is_the_one_that_runs.py +0 -0
  312. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_question_ranking_is_one_scale.py +0 -0
  313. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_rate_arm_declines_what_it_used_to_guess.py +0 -0
  314. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_readme_decline_count_is_derived.py +0 -0
  315. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_readme_name_count_is_derived.py +0 -0
  316. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_readme_names_the_forms_load_model_takes.py +0 -0
  317. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_rename_table_names_real_things.py +0 -0
  318. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_sample_floor_says_whether_it_is_reachable.py +0 -0
  319. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_schema_names_every_sub_envelope.py +0 -0
  320. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_schema_names_what_a_sub_envelope_carries.py +0 -0
  321. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_session_keeps_its_own_ledger.py +0 -0
  322. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_verb_that_used_the_number_names_it.py +0 -0
  323. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_the_world_model_can_be_exercised_over_mcp.py +0 -0
  324. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_threshold_overrides.py +0 -0
  325. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_timestamped_ingestion.py +0 -0
  326. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_traverse_vocabularies.py +0 -0
  327. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_trend_widens_and_does_not_point.py +0 -0
  328. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_two_actions_at_two_instants_are_not_one.py +0 -0
  329. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_two_bare_series_are_joinable.py +0 -0
  330. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_two_claims_on_one_edge_are_two_questions.py +0 -0
  331. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_two_offset_couplings_from_one_source_are_both_charged.py +0 -0
  332. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_two_plans_that_cost_the_same_compare_equal.py +0 -0
  333. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_two_rules_can_close_a_loop_neither_closes_alone.py +0 -0
  334. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_two_settings_of_one_property_do_not_add.py +0 -0
  335. {arbiter_engine-0.2.7 → arbiter_engine-0.2.9}/tests/test_unread_properties.py +0 -0
@@ -109,15 +109,26 @@ jobs:
109
109
  GH_TOKEN: ${{ github.token }}
110
110
  run: gh release upload "$GITHUB_REF_NAME" dist/* --clobber
111
111
 
112
- # PUBLISHING IS OFF UNTIL THE INDEX SIDE EXISTS, and the marker is a file
113
- # rather than a setting so the change that turns it on is reviewable.
112
+ # PUBLISHING IS DONE BY HAND, BY RULING, and the marker is a file rather
113
+ # than a setting so the change that turns it back on is reviewable.
114
114
  #
115
- # Trusted Publishing needs a publisher registered on PyPI naming this
116
- # repository, this workflow file and this environment. Until that is
117
- # registered, minting fails -- and a job that is red on every release day
118
- # is one people learn to skip, which is the argument `verify-published.yml`
119
- # already makes about itself. So: build and attach always, publish only
120
- # when the marker says the other side is ready.
115
+ # It was ON from 2026-09-03 to 2026-09-25 and never published a file. The
116
+ # job below runs on the TAG; the release runbook uploads BEFORE the tag,
117
+ # so the repository never advertises a version the index does not serve.
118
+ # For a release made that way the publish job can only ever meet a version
119
+ # already on the index, so approving it could only fail -- and fourteen tag
120
+ # runs, v0.1.11 through v0.2.7, sat waiting with no conclusion instead.
121
+ # That is the two paths being mutually exclusive, not somebody forgetting
122
+ # to click.
123
+ #
124
+ # BOTH SETTINGS IT NEEDS STILL EXIST, and settings do not show in a diff,
125
+ # so they are stated here rather than lost with the marker. A `pypi`
126
+ # environment with a required reviewer (measured 2026-09-25), and a PyPI
127
+ # trusted publisher naming this repository, this workflow and that
128
+ # environment (verified at registration on 2026-09-03; no upload has ever
129
+ # used it). Turning publishing back on is a change to the UPLOAD ORDER
130
+ # first -- tag, then publish -- and the marker second. The marker alone
131
+ # restores exactly the waits described above.
121
132
  - name: Is publishing from CI enabled?
122
133
  id: gate
123
134
  run: |
@@ -126,12 +137,13 @@ jobs:
126
137
  else
127
138
  echo "enabled=false" >> "$GITHUB_OUTPUT"
128
139
  echo "::notice::CI built these artifacts from the tag and attached them" \
129
- "to the Release. Publishing to the index is still done by hand." \
130
- "To hand it over: create the 'pypi' environment in this" \
131
- "repository's settings, register a PyPI trusted publisher naming" \
132
- "this repository, release.yml and that environment, then add" \
133
- ".github/PUBLISH_FROM_CI. The publish job stays behind the" \
134
- "environment's approval either way."
140
+ "to the Release. Publishing to the index is done by hand, before" \
141
+ "the tag, so this repository never advertises a version the index" \
142
+ "does not serve. The 'pypi' environment and the PyPI trusted" \
143
+ "publisher both still exist. To hand publishing to CI, change the" \
144
+ "release order to tag-then-publish FIRST, then add" \
145
+ ".github/PUBLISH_FROM_CI: the marker alone brings back a publish" \
146
+ "job that can only meet a version already on the index."
135
147
  fi
136
148
 
137
149
  # The bytes that were attached, kept for the publish job. NOT a rebuild
@@ -34,6 +34,65 @@ 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
+
57
+ ## [0.2.8] — 2026-09-25
58
+
59
+ ### Added
60
+
61
+ - **`target_reading_set_aside` (`TARGET_READING_SET_ASIDE`), a stamp on every
62
+ `inference` answer whose target had a reading of its own.** `infer` answers whether everything ELSE implicates
63
+ the target, so the target's own reading is left out -- and nothing said so. A
64
+ supply at 9.0 kV, below its own critical floor, got the posterior it gets at a
65
+ healthy 11.0 kV. `checked.target_reading` now carries what the reading would
66
+ have set (`faulty` or `clean`) and the target's own worst severity at any
67
+ level. No posterior changes.
68
+ - **Each `hypothesize` candidate carries `own_reading`**, the same row for that
69
+ candidate: `None` when it was unread, which is the case `evidence_needed` is
70
+ written for. Measured before it: a feeder read CLEAN ranked first at 0.962
71
+ with its own current named as the evidence needed.
72
+
73
+ ### Fixed
74
+
75
+ - **`hypothesize` carries the stamps of the inferences behind its ranking.** It
76
+ dropped all of them, so a ranking computed against this engine's default
77
+ evidence floor arrived without `evidence_severity_not_declared`, while the
78
+ verb's own docstring told the reader to check for that stamp.
79
+ - **An intervention on the target answers exactly.** `infer(t, do={t: v})`
80
+ returned the same posterior for `v = 0` and `v = 1` (0.984981 on the
81
+ specimen), because the forced value was set aside with the reading. It
82
+ returns `v` now, with method `intervention`, and files nothing.
83
+ - **Twelve published sentences that read as broken English.** Seven docstrings
84
+ and comments (`Until this returned two values`, `the ONLY rule until, and`)
85
+ and five lines of `evidence/` (`the recursive-examination framework
86
+ framework`) came out of the step that removes internal references. They now
87
+ read as sentences. No behaviour changes.
88
+
89
+ ### Changed
90
+
91
+ - **Releases are uploaded to the index by hand, and `.github/PUBLISH_FROM_CI` is
92
+ retired.** The CI publish job it enabled runs on the tag, after the upload the
93
+ release procedure makes first, so it never published a file. CI still builds
94
+ each release from its tag and attaches the files to the GitHub Release.
95
+
37
96
  ## [0.2.7] — 2026-09-25
38
97
 
39
98
  ### 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.7"
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"
@@ -1105,7 +1105,18 @@ this is a verb rather than a filter over `traverse`. On the shipped specimen,
1105
1105
  asking about the supply with the feeder OBSERVED faulty gives 0.679054 and with
1106
1106
  the feeder FORCED faulty gives 0.05 -- the same node in the same state, a factor
1107
1107
  of thirteen apart. Seeing a thing fail is evidence about what feeds it; breaking
1108
- it yourself is not.
1108
+ it yourself is not. Forcing the TARGET itself answers exactly: `do={t: v}`
1109
+ returns `v`, method `intervention`.
1110
+
1111
+ **The target's own reading is not evidence about the target.** `infer` asks
1112
+ whether everything ELSE implicates it, because conditioning on its own state
1113
+ would answer 1 or 0 by construction. So a supply in critical breach and the
1114
+ same supply healthy get one posterior between them -- correct for that
1115
+ question, and the answer says so: `target_reading_set_aside` is stamped whenever
1116
+ the target had a reading, and `checked.target_reading` names the state it would
1117
+ have set and the target's own worst severity. `hypothesize` carries the same
1118
+ row per candidate as `own_reading`, so a candidate the graph implicates can be
1119
+ read beside what its own meter already said.
1109
1120
 
1110
1121
  ### What counts as faulty evidence: `causal.evidence_severity`
1111
1122
 
@@ -1249,7 +1260,8 @@ which number to distrust.
1249
1260
  They arrive in an `assumptions` list on the `simulation` and `plan`
1250
1261
  sub-envelopes, and on each `plan` candidate where the candidates differ. An
1251
1262
  EMPTY list means the engine made none of them, which is a different claim from
1252
- the key being absent.
1263
+ the key being absent. The `inference` sub-envelope -- what `infer` and
1264
+ `hypothesize` return -- carries the list only when it has something in it.
1253
1265
 
1254
1266
  | stamp | what it discloses |
1255
1267
  |---|---|
@@ -1257,6 +1269,7 @@ the key being absent.
1257
1269
  | `time_course_not_declared` | the time course crossed was not declared; this engine supplied a delay or a time constant, or both |
1258
1270
  | `evidence_severity_not_declared` | `infer` read the last check against THIS engine's severity floor, because the model declared none. Declare `causal.evidence_severity:` to choose it |
1259
1271
  | `evidence_severity_unusable` | the model DID declare `causal.evidence_severity:` and this engine could not use it, so the floor above was its own. Carried beside the stamp above, never instead of it; `model_describe` names the value that was refused |
1272
+ | `target_reading_set_aside` | the queried entity had a reading of its own and the posterior was computed without it -- the question answered is whether everything else implicates it. `checked.target_reading` says what the reading would have set, and the target's own worst severity |
1260
1273
  | `steady_state_reached` | the horizon outlasted the transient, so the value reported is the settled one |
1261
1274
  | `series_edges_composed_exactly` | two couplings in series were composed by the exact cascade response |
1262
1275
  | `series_edges_compose_by_product` | two couplings in series were composed by multiplying response fractions, which is an approximation |
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: arbiter-engine
3
- Version: 0.2.7
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"
@@ -2526,7 +2535,19 @@ def hypothesize(session: EngineSession, entity_id: str,
2526
2535
  specimen: with both panels in the warning band the supply outranks the
2527
2536
  feeder 0.0104 to 0.0018, and with both below critical the feeder ranks first
2528
2537
  at 0.962. Same topology, same question, opposite answers — so read the
2529
- `evidence_severity_not_declared` stamp before reading this ranking.
2538
+ `evidence_severity_not_declared` stamp before reading this ranking. Since
2539
+ the ranking carries it: every stamp an inference behind a candidate
2540
+ applied rides on `hypothesis.assumptions`. Before that the verb dropped
2541
+ them, and this paragraph told the reader to look for a stamp that was never
2542
+ there.
2543
+
2544
+ **A candidate's own reading is not part of its score.** Each posterior
2545
+ answers whether the REST of the graph implicates that candidate, and
2546
+ `own_reading` says what the candidate's own meter already said -- `None`
2547
+ when unread, which is the case `evidence_needed` is for. Measured: a feeder
2548
+ read clean at 300 A ranked first at 0.962 with its own current named as the
2549
+ evidence needed. The ranking is right about the graph; the row now shows
2550
+ the reading beside it.
2530
2551
  """
2531
2552
  from arbiter_engine.inference.hypothesis import (
2532
2553
  hypothesize as _hypothesize)
@@ -2586,6 +2607,18 @@ def infer(session: EngineSession, target: str,
2586
2607
  Without `report_above` there is no finding, on the same rule as `project`
2587
2608
  and `discover`: the posterior is computed and reported, and whether it is
2588
2609
  alarming is not the engine's to decide.
2610
+
2611
+ THE TARGET'S OWN READING IS NOT EVIDENCE. The question answered is whether
2612
+ everything ELSE implicates the target -- conditioning on its own state would
2613
+ return 1 or 0 by construction. When the target had a reading, the answer is
2614
+ stamped `target_reading_set_aside` and `checked.target_reading` says what
2615
+ the reading would have set (`faulty` or `clean`) and the target's own worst
2616
+ severity at any level. A supply in critical breach and the same supply
2617
+ healthy get one posterior between them; that is correct for the question,
2618
+ and until a later change nothing on the answer said which question it was.
2619
+
2620
+ An intervention on the target itself answers exactly: `do={t: v}` returns
2621
+ `v`, method `intervention`, and files nothing.
2589
2622
  """
2590
2623
  if session.model is None:
2591
2624
  return unavailable_envelope("no domain model loaded")
@@ -6,7 +6,7 @@ not a warning and not a finding: it is the engine saying which of its own
6
6
  choices the value in front of you depends on, so that a reader who disagrees
7
7
  with the choice knows which number to distrust.
8
8
 
9
- WHY THEY LIVE HERE. Until they lived as bare string literals at twenty
9
+ WHY THEY LIVE HERE. Until a later change they lived as bare string literals at twenty
10
10
  sites across four modules, and nothing anywhere held the list. The cost was not
11
11
  hypothetical. An outside comparison of this engine reproduced all three of its
12
12
  ENUMERATED vocabularies exactly -- the fourteen decline reasons, the six gap
@@ -85,6 +85,23 @@ EVIDENCE_SEVERITY_NOT_DECLARED = "evidence_severity_not_declared"
85
85
  #: duplicate what is there.
86
86
  EVIDENCE_SEVERITY_UNUSABLE = "evidence_severity_unusable"
87
87
 
88
+ #: The queried entity had a reading of its own, and the posterior was computed
89
+ #: WITHOUT it. `infer` answers *does the rest of the graph implicate this
90
+ #: entity*, which is the informative question -- conditioning on the target's
91
+ #: own state would answer 1 or 0 by construction -- and the engine chose that
92
+ #: question on the caller's behalf. What the reading said is on the answer as
93
+ #: `checked.target_reading`: the state it would have set, and the target's own
94
+ #: worst severity at any level.
95
+ #:
96
+ #:, and it went unsaid for as long as the verb has existed. Measured on
97
+ #: the substation specimen with the feeder unread: the supply at 9.0 kV, below
98
+ #: its own critical floor, gets exactly the posterior it gets at a healthy
99
+ #: 11.0 kV, 0.670634 both times, because the one reading that would have
100
+ #: settled the question was the one left out. The other direction is worse. A feeder READ CLEAN is ranked
101
+ #: first by `hypothesize` at 0.962 with its own meter named as the evidence
102
+ #: needed -- the reading it had already taken and set aside.
103
+ TARGET_READING_SET_ASIDE = "target_reading_set_aside"
104
+
88
105
  #: The horizon was long enough that the transient had finished, so the value
89
106
  #: reported is the steady state rather than a point on the way to it.
90
107
  STEADY_STATE_REACHED = "steady_state_reached"
@@ -239,6 +256,7 @@ ASSUMPTION_STAMPS: Tuple[str, ...] = (
239
256
  TIME_COURSE_NOT_DECLARED,
240
257
  EVIDENCE_SEVERITY_NOT_DECLARED,
241
258
  EVIDENCE_SEVERITY_UNUSABLE,
259
+ TARGET_READING_SET_ASIDE,
242
260
  STEADY_STATE_REACHED,
243
261
  SERIES_EDGES_COMPOSED_EXACTLY,
244
262
  SERIES_EDGES_COMPOSE_BY_PRODUCT,
@@ -75,7 +75,7 @@ domain:
75
75
  #
76
76
  # The role rule is single-value: `role: percentage` range-checks this
77
77
  # reading against 0-100 and would behave identically if `level_pct` were
78
- # deleted from the model. That was the ONLY rule until, and this
78
+ # deleted from the model. That was the ONLY rule until a later change, and this
79
79
  # comment said `two independent readings that should agree` until
80
80
  # 2026-08-16 -- the reading the use-case catalogue carried in eleven
81
81
  # places, and the reason both were reported from outside on the same day.
@@ -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:
@@ -46,6 +46,7 @@ from __future__ import annotations
46
46
 
47
47
  from typing import Any, Dict, List, Optional, Set, Tuple
48
48
 
49
+ from ..assumptions import ASSUMPTION_STAMPS
49
50
  from ..subenvelope import Decline, SubEnvelope
50
51
  from .causal import CausalGraph, causal_subgraph
51
52
  from .runner import Query, run_inference
@@ -151,10 +152,18 @@ def hypothesize(session: Any, entity_id: str, *,
151
152
  return SubEnvelope("inference", checked, not_checked=declines), []
152
153
 
153
154
  ranked: List[Dict[str, Any]] = []
155
+ # THE STAMPS OF EVERY INFERENCE THIS RANKING RESTS ON. Each
156
+ # candidate is scored by `run_inference`, which stamps the defaults it
157
+ # applied; this verb used to drop them all, so a ranking computed against
158
+ # the engine's evidence floor carried no `evidence_severity_not_declared`
159
+ # while the verb's own docstring told the reader to check for it.
160
+ stamps: set = set()
154
161
  for node, hops in candidates:
155
162
  sub = run_inference(session, Query(target=node), report_above=None)
156
163
  payload = sub.to_dict()
157
- posterior = (payload.get("checked") or {}).get("posterior")
164
+ checked_here = payload.get("checked") or {}
165
+ posterior = checked_here.get("posterior")
166
+ stamps.update(payload.get("assumptions") or ())
158
167
  entity_type = graph.entity_type.get(node, "")
159
168
  properties = _readable_properties(session.model, entity_type)
160
169
  ranked.append({
@@ -165,6 +174,13 @@ def hypothesize(session: Any, entity_id: str, *,
165
174
  # The FIRST declared readable property, which is the model's own
166
175
  # ordering rather than this verb's opinion about which matters.
167
176
  "evidence_needed": (f"{node}.{properties[0]}" if properties else None),
177
+ # WHAT THAT READING ALREADY SAID, when it was taken. The
178
+ # posterior above leaves the candidate's own reading out, which is
179
+ # what makes it a ranking at all; without this row a feeder READ
180
+ # CLEAN came back first at 0.962 with its own meter as the evidence
181
+ # needed, sending an operator to take a reading already taken.
182
+ # `None` means unread -- the case `evidence_needed` is written for.
183
+ "own_reading": checked_here.get("target_reading"),
168
184
  "test_action": _test_action(session.model, entity_type),
169
185
  })
170
186
 
@@ -179,4 +195,9 @@ def hypothesize(session: Any, entity_id: str, *,
179
195
  if r["posterior"] is not None and r["posterior"] >= report_above]
180
196
  checked["reported"] = len(ranked)
181
197
 
182
- return SubEnvelope("inference", checked, not_checked=declines), ranked
198
+ # In the vocabulary's own order, so one set of findings always reports one
199
+ # list; a stamp outside the published tuple sorts last rather than vanishing.
200
+ order = {stamp: i for i, stamp in enumerate(ASSUMPTION_STAMPS)}
201
+ assumptions = tuple(sorted(stamps, key=lambda s: (order.get(s, len(order)), s)))
202
+ return (SubEnvelope("inference", checked, not_checked=declines,
203
+ assumptions=assumptions), ranked)
@@ -23,7 +23,8 @@ from typing import (Any, Dict, FrozenSet, List, Optional, Sequence, Set,
23
23
  Tuple)
24
24
 
25
25
  from ..assumptions import (EVIDENCE_SEVERITY_NOT_DECLARED,
26
- EVIDENCE_SEVERITY_UNUSABLE)
26
+ EVIDENCE_SEVERITY_UNUSABLE,
27
+ TARGET_READING_SET_ASIDE)
27
28
  from ..subenvelope import Decline, SubEnvelope
28
29
  from ..twin.gap import GAP_CONFIDENCE_THRESHOLDS as _GAP_WEIGHT
29
30
  from ..twin.topology import GapType, TopologyGap, TopologyQuestion
@@ -79,7 +80,7 @@ def evidence_severities(model) -> Tuple[FrozenSet[str], bool, bool]:
79
80
  see; `[critical, hihg]` therefore becomes the default rather than quietly
80
81
  becoming `[critical]`.
81
82
 
82
- `unusable` SEPARATES TRYING FROM NOT TRYING. Until this returned
83
+ `unusable` SEPARATES TRYING FROM NOT TRYING. Until a later change this returned
83
84
  two values and an author who mistyped a severity got the same bare
84
85
  `evidence_severity_not_declared` as one who declared nothing -- the reading
85
86
  an outside review reproduced was *not declared*, therefore *my file did not
@@ -137,6 +138,40 @@ def evidence_from(session, graph: CausalGraph,
137
138
  return observed, unobserved
138
139
 
139
140
 
141
+ #: Severity order for reporting the worst one, most urgent first. Ties in
142
+ #: `priority_score` (WARNING and MEDIUM share one) fall to declaration order, so
143
+ #: the same findings always report the same word.
144
+ _SEVERITY_ORDER: Tuple[Severity, ...] = tuple(Severity)
145
+
146
+
147
+ def _own_severity(session, entity_id: str) -> Optional[str]:
148
+ """The most urgent severity the last check gave `entity_id`, at ANY level.
149
+
150
+ Deliberately not filtered by the evidence floor. A panel carrying a warning
151
+ under an engine floor of `high` counted as CLEAN, and a reader looking at
152
+ the set-aside reading is owed both facts -- what the floor made of it and
153
+ what the check actually said -- or *clean* reads as *nothing was found*.
154
+ """
155
+ result = getattr(session, "_last_result", None)
156
+ if result is None:
157
+ return None
158
+ worst: Optional[Severity] = None
159
+ for problem in list(getattr(result, "problems", ()) or ()) + \
160
+ list(getattr(result, "warnings", ()) or ()):
161
+ if str(getattr(problem, "entity_id", "")) != entity_id:
162
+ continue
163
+ raw = getattr(problem, "severity", None)
164
+ try:
165
+ severity = Severity(str(getattr(raw, "value", raw)).lower())
166
+ except ValueError:
167
+ continue
168
+ if worst is None or (
169
+ (severity.priority_score, _SEVERITY_ORDER.index(severity))
170
+ < (worst.priority_score, _SEVERITY_ORDER.index(worst))):
171
+ worst = severity
172
+ return None if worst is None else worst.value
173
+
174
+
140
175
  def _surgery(graph: CausalGraph, do: Dict[str, int]) -> CausalGraph:
141
176
  """Cut the incoming edges of every intervened node. THIS is the difference
142
177
  between `do(x)` and observing x, and the reason a hypothetical cannot be
@@ -220,6 +255,22 @@ def run_inference(session, query: Query,
220
255
  evidence={"cycle": loop}))
221
256
  return SubEnvelope("inference", checked, findings, declines, questions)
222
257
 
258
+ # AN INTERVENTION ON THE TARGET IS THE ANSWER. `do(x=v)` sets x,
259
+ # so P(x faulty | do(x=v)) is v, whatever the evidence says and however the
260
+ # graph is wired. Until this branch the intervened value went into the
261
+ # evidence and was then popped with the target's reading below, so the
262
+ # surgery cut x's parents and the elimination answered for an x nobody had
263
+ # set: measured on the specimen, `do={fdr-1: 0}` and `do={fdr-1: 1}` both
264
+ # returned 0.984981. Answered here, BEFORE the backdoor check, because an
265
+ # answer that reads no edge cannot be unidentifiable. Nothing is filed and
266
+ # nothing is reported: a value the caller forced is not a prediction about
267
+ # the world, and a finding would report the caller's own act back to them.
268
+ if query.target in query.do:
269
+ checked["answered"] = 1
270
+ checked["method"] = "intervention"
271
+ checked["posterior"] = float(query.do[query.target])
272
+ return SubEnvelope("inference", checked, findings, declines, questions)
273
+
223
274
  working = _surgery(graph, query.do) if query.do else graph
224
275
  latent = _open_backdoor_latent(working, query)
225
276
  if latent is not None:
@@ -235,17 +286,34 @@ def run_inference(session, query: Query,
235
286
  observed, unobserved = evidence_from(session, working, severities)
236
287
  for node in query.do:
237
288
  observed[node] = query.do[node]
238
- observed.pop(query.target, None)
289
+ # The target's own state is left out, because conditioning on it answers 1
290
+ # or 0 by construction. What is asked is whether EVERYTHING ELSE implicates
291
+ # it -- and since the answer says that is what was asked.
292
+ set_aside = observed.pop(query.target, None)
239
293
  checked["evidence"] = len(observed)
240
294
  checked["unobserved"] = len(unobserved)
241
295
  # from here down the answer depends on WHICH severities counted
242
- # as faulty, so every exit below carries the disclosure. The two returns
296
+ # as faulty, so every exit below carries the disclosure. The returns
243
297
  # above this line are refusals decided from the GRAPH alone -- a cycle, an
244
- # open backdoor -- and read no evidence, so stamping them would name a
245
- # choice that did not touch the answer.
298
+ # open backdoor -- or an intervention that fixes the answer, and read no
299
+ # evidence, so stamping them would name a choice that did not touch it.
246
300
  stamps: Tuple[str, ...] = () if floor_declared else (
247
301
  (EVIDENCE_SEVERITY_NOT_DECLARED,)
248
302
  + ((EVIDENCE_SEVERITY_UNUSABLE,) if floor_unusable else ()))
303
+ # AND WHICH READING WAS NOT USED. A target in breach answered
304
+ # exactly as a healthy one did, with nothing on the envelope saying its own
305
+ # reading had been excluded: measured, the supply at 9.0 kV and at 11.0 kV
306
+ # both 0.670634. Stamped whenever the target HAD a reading, clean as well
307
+ # as faulty -- a clean reading set aside misleads just as far, the other
308
+ # way. `severity` is the target's own worst finding at any level, beside
309
+ # the state the evidence floor made of it, because under the engine's floor
310
+ # a warning counts as clean and *clean* alone would read as *nothing found*.
311
+ target_reading: Optional[Dict[str, Any]] = None
312
+ if set_aside is not None:
313
+ target_reading = {"state": "faulty" if set_aside else "clean",
314
+ "severity": _own_severity(session, query.target)}
315
+ checked["target_reading"] = target_reading
316
+ stamps = stamps + (TARGET_READING_SET_ASIDE,)
249
317
 
250
318
  relevant = _relevant_edges(working, query, observed)
251
319
  defaulted = sorted(f"{s}->{t}" for (s, t) in relevant
@@ -313,14 +381,14 @@ def run_inference(session, query: Query,
313
381
  elif posterior >= report_above:
314
382
  findings.append(_posterior_finding(
315
383
  session, working, query, posterior, observed, unobserved,
316
- report_above))
384
+ report_above, target_reading))
317
385
 
318
386
  return SubEnvelope("inference", checked, findings, declines, questions,
319
387
  assumptions=stamps)
320
388
 
321
389
 
322
390
  def _posterior_finding(session, graph, query, posterior, observed,
323
- unobserved, report_above):
391
+ unobserved, report_above, target_reading=None):
324
392
  from ..interfaces import Problem
325
393
  entity = session.entities.get(query.target)
326
394
  return Problem.from_entity(
@@ -335,6 +403,9 @@ def _posterior_finding(session, graph, query, posterior, observed,
335
403
  "cpt_sources": graph.sources(),
336
404
  "evidence_set": sorted(observed),
337
405
  "unobserved": sorted(unobserved),
406
+ # a finding travels without its envelope, so the
407
+ # reading it did not use travels with it.
408
+ "target_reading": target_reading,
338
409
  "root_prior": ROOT_PRIOR,
339
410
  "root_prior_source": "default",
340
411
  "do": dict(query.do)})
@@ -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
@@ -138,7 +138,7 @@ def build(entities: int, observations: int, breach_fraction: float):
138
138
  """A fresh session with `entities` units, a `breach_fraction` of them bad.
139
139
 
140
140
  Returns the session and the two halves of its construction cost SEPARATELY.
141
- They were one number until, and the number was unreadable: loading
141
+ They were one number until a later change, and the number was unreadable: loading
142
142
  the model is flat in ENTITY count -- it loads a model, not entities -- while
143
143
  the feed is linear, so a single `build` figure hides which half a consumer
144
144
  pays again on the next cycle. That is the question the cost table gets
@@ -1744,7 +1744,7 @@ class TopologyTraverser:
1744
1744
  builder seeded, which is how a walk that evaluated one invariant came
1745
1745
  to report four.
1746
1746
 
1747
- **BOUNDEDNESS only** — and, until, only HALF of BOUNDEDNESS.
1747
+ **BOUNDEDNESS only** — and, until a later change, only HALF of BOUNDEDNESS.
1748
1748
  The body compared against `warning` and `critical` and never against
1749
1749
  `lower_warning` / `lower_critical`, so a declared FLOOR was invisible
1750
1750
  here while `UnifiedAxiomReasoner` reported it: a traversal over a