Skip to content

refactor(decision-engine): strip Part 1 — advisory-only engine as an independent module - #204

Merged
g1e2x87 merged 31 commits into
mainfrom
feat/decision-engine-strip-p1
Nov 14, 2026
Merged

g1e2x87 merged 31 commits into
mainfrom
feat/decision-engine-strip-p1

Conversation

@g1e2x87

@g1e2x87 g1e2x87 commented Nov 13, 2026

Copy link
Copy Markdown
Member

Summary

Part 1 of the decision-engine strip: the engine becomes advisory-only and moves into its own module, src/decision_engine/. Everything it shared with the rest of the crate gets a neutral home. Historical data and recovery stay intact: the legacy decision store, the key, the record writers, terminal replay and AUTH-02 are kept. Deleting them is Part 2.

Groups, one commit each:

  • 04: the chat turn binding moves out of chat/decision_source.
  • 05a: every turn opens without prediction (R9), the engine only gives advice, shared items get neutral homes, and Full autonomy runs without permission limits (R51). CHANGELOG has BREAKING (operators) lines for both.
  • 05a1: selection goes through AdvisoryProbe. It annotates with the real hard-gate outcomes and never filters.
  • 05aS: neutral homes for the remaining shared items.
  • 05a2: the engine core moves into src/decision_engine.
  • 05a3: the turn runtime context owns the turn state that is not a gate.
  • 05a4: turn-runtime rehome (R24–R29, R53, R54).

The audit-fix commits that follow these groups address Codex code audit r1, r2 and r3. The last commit records how this branch was rebased onto main 6676a46, which includes the landed engine switch.

Owner rulings

  • Full means no permission limits. Integrity checks, budgets, recursive money and spawn depth still apply.
  • Engine cost is charged to the turn's own budget and skipped when the budget is tight. A request is never refused because of the engine.
  • Dev phase: no legacy compatibility layer.

Rebase onto main

The Clarify degrade and the drift-path degrade were built into the prediction hop, so they are removed together with it. The following switch behaviour is kept:

  • the confirms_plain_answer acceptance of unbound and denied attempts;
  • the TurnScheduler requeue fix;
  • the quinn-udp bump;
  • the control tools on plain answers.

The last commit lists every test that was deleted or rewritten.

Audits

  • Spec: coordination/REQ-DECISION-ENGINE-STRIP-P1.md, spec audits r1–r4.
  • Code: Codex code audit r1–r3, in coordination/reports/STRIP-P1-code-audit-r{1,2,3}.md.

Verification

cargo check --all-targets --all-features passes with zero errors and zero warnings. PR CI is the full gate.

…t/decision_source

R1: chat/binding.rs now owns ChatProviderBinding, ChatAdmissionGuard,
ChatOperatorSpawn, ChatSourceReceipt, ChatAdmissionMode, ChatTurnBinding
(with TurnSourceProof), CapturedChatTrace, ChatDecision, the persistence and
resume-failure replies and the test-mock mailbox fixture. chat/decision_source.rs
keeps only the advice adapter: issue_decision (with analyze_advisory),
decision_advice and decision_routing, until R9.

R2: the admission_failure_tests module moves to chat/binding/tests.rs and
chat/binding/tests/ with names and bodies unchanged; the advisory test
advisory_decide_mode_projects_prediction_and_lazily_opens_store stays with the
adapter. Importers and architecture path strings are retargeted.

R3: the adapter calls only the advisory method set.
…homes, Full autonomy without permission limits

Requirements done: R4, R5, R6, R7, R8 (shared contract types, capability
policy, runtime key, logical clock and scanner helpers in neutral homes),
R55 (effective engine backend), R11 (AdmissionProbe / HardGatesProbe),
R60 (TurnRuntimeContext, AdmissionFactsV1, VerifiedResumeEvidenceV1),
R9 (free-standing DecisionAdvisory, one prediction-free turn opening,
budget-charged analysis, V-17 execution-owned resume check), R10
(effective telemetry cap, advisory driver tests), R51 (Full autonomy has
no permission limits), R58 (supervised working-root approval test) and
the controller ruling R9-DEAD (the legacy prediction/selection pipeline
is deleted with its exclusive helpers; nothing is hidden behind
cfg(test) or allow). CHANGELOG BREAKING (operators) lines for R9 and R51.

R9-DEAD cascade: causal_tree/{decision_rehearsal,exploration}.rs and
turn_decision/latency_snapshot.rs deleted; decision_select keeps only
Selection::Commit, decision_score keeps SCORER_VERSION + CandidateScore;
EngineDecision without audit trace, scores pipeline or call fingerprints;
CandidateSet without discoverable families or approval-only lists;
ObservedStepKind::Retrieve, HopLineage::After and the observed artifacts
removed (canonical and projection JSON still emit "artifacts":[] and the
record's empty artifact sequences byte-identically); the causal_events
admission deferral machinery, the hop's TurnAccounting, the selected
memory formatters, the progress diagnostic and the clock stall hook are
deleted.

New tests: agent::turn_tests::engine_switch::{engine_on_and_off_open_identical_turns,
tight_budget_skips_analysis_and_never_refuses_the_request},
runtime::turn_decision::tests::exact_call_approval::historical_resume_survives_prediction_free_opening,
runtime::decision_advisory::tests::{decision_advisory_runs_without_turn_decision_service,
forced_drop_is_reported_only_for_its_workspace, late_advisory_drain_warns_exactly_once,
pre_mode_status_reports_effective_cap_zero},
tools::execution::tests::full_autonomy_has_no_permission_limits,
runtime::turn_decision::owner_filesystem::working_root_tests::supervised_working_root_approval_executes_once_and_denial_never_executes,
causal_tree::admission_probe::tests::{hard_gates_probe_reproduces_pre_separation_candidate_sets,
exact_call_scope_is_independent_of_approval_outcome},
config::schema::decision_engine_config_tests::{effective_backend_prefers_explicit_table_over_differing_legacy,
explicit_empty_engine_backend_uses_defaults_not_legacy, legacy_only_backend_is_copied_into_the_engine}.

Deleted or trimmed tests (file: test (reason)):
src/causal_tree/decision_rehearsal.rs: write_and_effectively_write_candidates_never_call_probes
src/causal_tree/decision_rehearsal.rs: serial_slow_probes_share_one_deadline
src/causal_tree/decision_rehearsal.rs: stale_catalog_and_probe_error_never_raise_score
src/causal_tree/decision_rehearsal.rs: duplicate_candidate_batch_calls_no_port
src/causal_tree/decision_rehearsal.rs: partial_required_availability_is_adverse
src/causal_tree/exploration.rs: exploration_forbidden_for_authority_and_hidden_effects
src/causal_tree/exploration.rs: sub_draw_resolution_rate_creates_no_false_propensity
src/causal_tree/decision_select.rs: r3_textless_surface_closes_every_abstention_with_refuse
src/causal_tree/decision_select.rs: r3_below_bar_destructive_asks_and_approved_exact_resume_commits
src/causal_tree/decision_select.rs: a_plain_turn_evidence_value_counts_as_sufficient
src/causal_tree/decision_select.rs: low_utility_tool_choice_gets_one_gated_metadata_recovery
src/causal_tree/decision_select.rs: read_only_near_tie_explores_with_recorded_propensities
src/causal_tree/decision_select.rs: near_tie_with_a_write_candidate_keeps_abstention
src/causal_tree/decision_select.rs: zero_legal_choice_mass_abstains_even_with_high_utility
src/causal_tree/decision_select.rs: equal_utility_uses_stable_candidate_id_tiebreak
src/causal_tree/decision_select.rs: low_choice_confidence_abstains_before_read_tool_commit
src/causal_tree/decision_select.rs: completed_discovery_settles_only_the_targetless_answer
src/causal_tree/decision_select.rs: stale_answer_recovers_only_through_a_passed_retrieval
src/causal_tree/decision_select.rs: missing_target_discovery_matches_supported_family_and_passed_candidate
src/causal_tree/decision_select.rs: missing_exact_direct_candidate_does_not_commit
src/causal_tree/decision_select.rs: exact_read_discovers_or_executes_without_a_redundant_retrieval
src/causal_tree/decision_select.rs: exact_current_tool_cannot_publish_below_the_execution_threshold
src/causal_tree/decision_select.rs: exact_source_configured_delegate_cannot_publish_below_threshold_and_margin
src/causal_tree/decision_select.rs: approval_requires_current_g4_only_proof_and_passed_terminal
src/causal_tree/decision_select.rs: multiple_approval_scopes_select_one_stable_verified_scope
src/causal_tree/decision_select.rs: confidence_fallback_requires_an_admitted_text_terminal
src/causal_tree/decision_select.rs: no_legal_path_needs_a_current_terminal_gate_result
src/causal_tree/decision_select.rs: no_legal_path_approval_keeps_its_verified_scope
src/causal_tree/decision_select.rs: deterministic_commit_retains_unit_propensity_without_exploration_distribution
src/causal_tree/decision_select.rs: confidence_abstention_answers_or_clarifies_on_every_surface
src/causal_tree/decision_select.rs: exact_tool_still_requires_every_execution_bar_and_cannot_repeat_failure
src/causal_tree/decision_score.rs: scorer_defaults_have_valid_positive_budget
src/causal_tree/decision_score.rs: risk_and_uncertainty_only_lower_execution_utility
src/causal_tree/decision_score.rs: execution_latency_still_lowers_the_score
src/causal_tree/decision_score.rs: each_risk_and_uncertainty_coordinate_lowers_an_interior_score
src/causal_tree/decision_score.rs: scorer_rejects_nan_and_invalid_weight_budget
src/causal_tree/decision_score.rs: score_expected_and_distribution_uncertainty_are_normalized
src/causal_tree/decision_score.rs: trusted_risk_floor_covers_every_effect_class
src/causal_tree/decision_score.rs: validated_choice_confidence_is_kept_out_of_candidate_features
src/causal_tree/decision_score.rs: risk_and_uncertainty_are_score_monotone
src/causal_tree/decision_engine.rs: exact_skill_and_loader_are_one_operation_but_distinct_skills_are_not
src/causal_tree/decision_engine.rs: another_named_effect_does_not_force_context_retrieval_before_the_only_named_read
src/causal_tree/decision_engine.rs: validated_audit_trace_keeps_numeric_scores_and_forced_codes_without_state_text
src/causal_tree/decision_engine.rs: outgoing_child_is_new_strict_subset_for_root_and_nested_worker
src/causal_tree/decision_engine.rs: outgoing_child_rejects_excluded_catalog_reference
src/causal_tree/decision_engine.rs: post_rehearsal_denial_cannot_be_restored_by_pre_commit
src/causal_tree/decision_engine.rs: approval_scope_cannot_be_restored_or_rebound
src/causal_tree/decision_engine.rs: exhausted_prediction_has_no_working_choice_mass
src/causal_tree/decision_engine.rs: selected_read_issuers_reject_over_bound_exact_source_queries
src/causal_tree/decision_engine.rs: selected_discovery_issues_only_pinned_visible_allowed_refs
src/causal_tree/decision_engine.rs: expired_turn_skips_backend_and_returns_a_typed_terminal
src/causal_tree/decision_engine/approval_selection_tests.rs: exact_approval_required_tool_requests_approval_instead_of_answering
src/causal_tree/decision_engine/approval_selection_tests.rs: exact_mention_without_call_fingerprint_does_not_answer
src/causal_tree/decision_engine/approval_selection_tests.rs: approval_only_tool_that_is_not_named_still_answers
src/causal_tree/decision_engine/approval_selection_tests.rs: unavailable_or_denied_approval_yields_no_request
src/causal_tree/decision_engine/approval_selection_tests.rs: exact_file_write_stays_on_the_approval_deferred_terminal_path
src/causal_tree/decision_engine/deadline_keep_tests.rs: deadline_keeps_commit_only_after_complete_scoring
src/causal_tree/decision_engine/deadline_keep_tests.rs: r4_deadline_between_scoring_and_selection_closes_without_a_model_answer
src/causal_tree/decision_engine/deadline_keep_tests.rs: o9_long_paths_and_output_contract_reaches_the_model
src/causal_tree/decision_engine/deadline_keep_tests.rs: o5_exact_reply_uses_model_answer
src/causal_tree/decision_engine/deadline_keep_tests.rs: o14_negations_and_ten_paths_do_not_refuse
src/causal_tree/decision_engine/deadline_keep_tests.rs: a_shell_explanation_never_executes_the_quoted_command
src/causal_tree/decision_engine/deadline_keep_tests.rs: failed_tool_cannot_execute_on_the_next_hop
src/causal_tree/decision_engine/deadline_keep_tests.rs: default_destructive_tool_cannot_reach_commit_bar
src/causal_tree/decision_engine/deadline_keep_tests.rs: default_credential_tool_cannot_reach_commit_bar
src/causal_tree/decision_engine/deadline_keep_tests.rs: r3_exact_read_selection_reason_preserves_the_abstention_column
src/causal_tree/decision_engine/resume_availability_tests.rs: resume_availability_is_single_current_reference_and_never_catalog_exposure
src/causal_tree/decision_engine/original_observation/tests.rs: actual_compiler_object_reaches_backend_and_scores_join_outcome
src/causal_tree/decision_engine/original_observation/tests.rs: missing_duplicate_and_overflow_observations_are_not_accepted
src/causal_tree/decision_engine/original_observation/tests.rs: original_request_score_and_source_mutations_are_rejected
src/causal_tree/decision_engine/original_observation/tests.rs: mismatched_model_cannot_create_original_observation
src/causal_tree/decision_engine/original_observation/material.rs: idle_material_has_no_fabricated_request_or_wire
src/causal_tree/decision_engine/original_observation/material.rs: unbound_send_cannot_be_certified_as_idle
src/causal_tree/decision_engine/original_observation/material.rs: oversized_actual_body_invalidates_capture
src/causal_tree/decision_engine/original_observation/material.rs: cancelled_scope_does_not_leak_into_next_capture
src/decision/factory.rs: only_all_semantic_validation_failures_close_the_configured_decision_path
src/causal_tree/outcome.rs: forced_choice_stays_out_of_calibration_and_builtin_shadow
src/runtime/turn_decision/tests/selected_memory.rs: selected_memory_service_rejects_foreign_turn_and_keeps_direct_core_after_read
src/runtime/turn_decision/tests/selected_memory.rs: builtin_agent_discovery_runs_a_bounded_dependent_hop
src/runtime/turn_decision/tests/selected_memory.rs: selected_discovery_is_source_bound_and_never_dispatches_a_tool
src/runtime/turn_decision/tests/selection_probes.rs: selected_session_projection_keeps_kind_and_distrusts_event_identity
src/runtime/turn_decision/tests/selection_probes.rs: selected_memory_artifacts_redact_before_projection_cut_and_distrust_row_metadata
src/runtime/turn_decision/tests/selection_probes.rs: selected_execute_retains_exact_gated_refs_without_dispatch
src/runtime/turn_decision/tests/performance.rs: actual_builtin_fixture_characterization
src/runtime/turn_decision/tests/performance/mcp_inventory.rs: owned_mcp_inventory_catalog_growth_profile
src/runtime/turn_decision/tests/performance/model_binding.rs: actual_compiler_binds_decision_model_before_real_http
src/runtime/turn_decision/tests/performance/model_allowlist.rs: owned_model_allowlist_catalog_growth_profile
src/runtime/turn_decision/tests/performance/phases.rs: actual_builtin_phase_characterization
src/runtime/turn_decision/tests/performance/model_binding/original_service_wire.rs: actual_configured_service_original_wire_ok
src/runtime/turn_decision/tests/performance/model_binding/original_service_wire.rs: actual_configured_service_original_wire_slow
src/runtime/turn_decision/tests/agent_decision.rs: agent_decision_cancellation_stops_before_backend_or_selected_read agent_decision_phases_follow_source_lineage_without_claiming_commit selected_retrieve_redecides_only_after_material_evidence_and_honors_hop_cap memory_rehearsal_rejects_wrong_principal_or_query_before_backend pinned_rehearsal_counts_only_visible_exact_refs_and_skill_family enabled_engine_without_recording_runs_generator_gates_and_builtin agent_initial_decision_respects_configured_four_kib_wire_limit r3_o9_o14_and_quoted_shell_fallbacks_publish_no_execution_refs clarify_without_gate_passed_answer_is_typed_refusal
REWRITTEN agent_decision.rs: record_builder_distinguishes_local_terminal_from_failed_compiled_request -> record_builder_records_the_prediction_free_opening_and_rejects_a_broken_chain (compiled-request half had no subject)
src/agent/terminal/replay.rs: prepared_replay_reads_pre_switch_request_approval_effect_from_recorded_hop (live hop can no longer select RequestApproval)
src/agent/turn_tests/engine_switch.rs: recorded_pre_switch_directive_replays_unchanged (RecordedDirective::RequestApproval unconstructible after R9)
src/causal_tree/directive.rs: approval_scope_uses_pinned_decision_and_call_digest (+ fixture recorded_request_approval_hop)
src/causal_tree/admission_probe/legacy_generator_tests.rs: supervised_generic_write_without_arguments_reports_scope_unavailable (NoLegalReason field removed); delegate_without_an_action_scope_cannot_offer_approval and unrelated_user_text_preserves_candidates_and_exact_name_adds_only_its_pair trimmed (terminal-only, resume-target, extend_selected, evidence_fingerprint halves)
tests/architecture_boundaries/decision_terminals.rs: recovery_cannot_publish_an_executable_tool, confidence_terminals_include_answer_and_cli_clarification (+ scanner_probes mutation_proof_recovery_execution, mutation_proof_confidence_terminal; decision-clock probe retargeted to turn_engine)
src/chat/binding/tests/operator_accounting.rs: actual_operator_task_decision_accounts_physical_send_and_drains, restored_operator_process_decision_accounts_physical_send_without_os_spawn (opening no longer sends a prediction)
src/agent/turn_tests/entry.rs: cli_readonly_shell_request_is_refused_by_policy_before_the_model, cli_turn_exact_named_mcp_write_without_review_ingress_closes_instead_of_answering (hop refusal/close selections)
src/chat/dispatcher/turn_characterization_tests.rs: enabled_interactive_backend_failure_publishes_provider_reply (hop Clarify abstention)
src/agent/turn_tests/engine_switch.rs: enabled_builtin_low_confidence_run_turn_publishes_plain_answer (engine-on refusal/abstention path; replaced by engine_on_and_off_open_identical_turns)
src/causal_tree/questions.rs: fixed_terminals_remain_choices_without_unused_predictions_on_the_wire (scorer extraction removed)
src/causal_tree/store/tests/mod.rs: capability_latency_uses_only_complete_single_call_owner_and_generation_samples, old_read_only_store_keeps_default_latency_without_a_tie_order_index (hop latency reader removed); src/runtime/turn_decision/tests/recorded_chain.rs: enabled_agent_service_records_only_a_successful_decision_chain trimmed (observed_capability_latency half)
src/runtime/turn_decision/latency_snapshot.rs: a_completed_latency_snapshot_preserves_the_scoped_samples, a_blocked_latency_snapshot_falls_back_without_waiting_for_the_read
src/causal_tree/state.rs: the_hop_loop_stops_when_a_hop_found_no_new_evidence (evidence_fingerprint removed); exact_calls test trimmed
src/runtime/turn_decision/tests/catalog_drift.rs: catalog_drift_after_selected_read_discards_retrieved_work_chain (selected read)
src/runtime/turn_decision/tests/discovery_surface.rs: non_english_discovery_reaches_admitted_family_without_full_registry, search_hits_are_admitted_for_the_next_request_through_the_same_gates, discovered_tools_are_capped_per_turn_and_admitted_in_search_rank_order, production_discovery_selects_a_skill_and_loads_its_body_only_on_execution (hop-selected Execute via probe backend)
src/runtime/turn_decision/tests/enabled_service.rs: validated_usage_and_provider_request_id_reach_the_durable_decision, cancellation_while_backend_pending_admits_no_decision_or_outcome (opening no longer calls the backend)
src/runtime/turn_decision/tests/catalog_drift.rs: catalog_drift_closes_with_fresh_unlinked_terminal_and_no_dispatch, cancellation_during_backend_and_catalog_drift_wins_before_recovery (hop backend/terminal recovery); ledger_probe.rs fixture file removed
src/runtime/causal_events/deferral/tests.rs: deferred_telemetry_remains_observable_and_concurrent_drains_preserve_order_with_live_guards (deferral machinery removed)
src/runtime/causal_events/deferral/independence_tests.rs: overlapping_admissions_release_independently_and_controls_bypass_live_deferral, a_live_guard_cannot_extend_its_deadline_and_readmission_releases_old_events (deferral machinery removed)
src/runtime/turn_decision/accounting/tests.rs: admission_releases_phase_writes_while_its_physical_accounting_is_retained (hop accounting and phase deferral removed)
src/causal_tree/state.rs: lineage_and_time_are_not_material, new_inputs_are_material, the_hop_lineage_pairs_hop_and_prior, debug_output_carries_no_private_text trimmed (later-hop lineage and artifact cells; the fixture step is now a Tool step without artifacts)
src/causal_tree/state/canonical_tests.rs: canonical_bytes_and_keyed_digests_match_independent_owned_oracle and canonical_preserves_observation_and_capability_sequence_order trimmed (artifact and later-hop cells); canonical_distinguishes_mixed_delivery_empty_optionals_and_maximum_hop -> canonical_distinguishes_mixed_delivery_and_empty_optionals (maximum hop removed); the empty-state golden bytes are unchanged
src/causal_tree/projection.rs: the_hop_and_the_prior_decision_are_projected_together trimmed to hop 0 (+ "artifacts":[]); any_state proptest strategy and sentinels_never_survive_the_projection lose the artifact and hop inputs
src/causal_tree/questions.rs: byte_budget_shrinks_summaries_without_changing_questions_or_candidates (no artifact summaries left to shrink)
src/causal_tree/questions/tests/ownership.rs: compiler_multiple_shrinks_budget_and_empty_summary_match_original -> compiler_budget_and_invalid_model_match_original (artifact shrink half removed)
src/causal_tree/admission_probe/legacy_generator_tests.rs: approval_only assertions now read the proposed RequestApproval candidates (approval_only_path_carries_concrete_operation_scope no longer sees op ids)
src/agent/memory_loader.rs: selected_memory_formatter_runs_only_for_admitted_memory_summaries, selected_memory_formatter_rejects_other_artifact_kinds_before_callback, selected_documents_do_not_enter_memory_formatter, selected_session_history_does_not_enter_memory_formatter, default_selected_context_preserves_evidence_types_and_memory_text, selected_memory_formatter_failure_is_not_replaced_with_unscoped_recall (selected-read artifacts removed)
src/agent/turn_tests/admission.rs: configured_mechanics_on_wall_clock_expires_when_decision_http_stalls_past_budget (opening no longer calls the decision endpoint)
src/agent/turn_tests/engine_surface.rs: a_spawned_sub_agent_inherits_the_parent_channels_tool_exclusions (required a prediction-committed parent Delegate); run_tool_call_loop_executes_read_only_tools_with_bounded_parallelism loses the hop progress diagnostic
src/skills/lifecycle/tests/acceptance/scenario_{a,b,e}.rs: a_wall_stall_past_the_latency_budget_still_answers; scenario_a.rs: advancing_the_logical_latency_budget_degrades_clarify_to_plain_answer (hop clock stall and Clarify degradation removed)
Unused test fixtures removed with them: AdmittedTurn::{with_enabled_builtin_model_and_mcp, ordinary_controlled_channel}, chat::binding::tests::recorded_plan_source, TurnDecisionContext::ordinary_fixture_prediction_verified, TurnDecisionService::install_ordinary_publication_fixture, McpTool test invocation counter, logical_clock apply_test_stall/arm_wall_stall/arm_logical_advance, turn_decision::progress_diagnostic.
…Probe

R12: AdmissionProbe gains filters(); HardGatesProbe returns true and keeps
every legacy branch unchanged. New AdvisoryProbe wraps the execution probe
the driver builds and returns false: at each appendix A site (exact mention,
named-file group, unbound approval, discovery eligibility, initial apply and
approval synthesis, final apply, no-legal-path, family G9/G2 prefilters,
family selection) admission only annotates; structural filters, order and
the candidate limit stay.

R13: DecisionAdvisory generates the analysed set with AdvisoryProbe over the
locally built HardGatesProbe (scoped so no non-Sync probe is held across the
prediction await). causal_adapter unchanged.

R14 tests (written, not run): causal_tree::admission_probe::tests::
{advisory_probe_annotates_blockers_without_filtering,
advisory_ordering_and_limits_match_structural_order};
hard_gates_probe_reproduces_pre_separation_candidate_sets extended to the
ten appendix B fixtures; runtime::decision_advisory::tests::
engine_driver_generates_with_advisory_probe.

select_family_candidate is pub(super) so the probe tests reach the G2
surface prefilter: a surface-excluded entry is hidden by the view itself,
so generate() never hands it to family selection.
R15/R52: DecisionId -> security/decision_id.rs; the neutral turn scope
(SideEffectMode, TurnSurfaceKind, ConstraintCode, TurnBudgets,
DeliveryConstraints, TrustedParts, TrustedState, RouterHistoryReadBinding,
exact_calls, model_admission) and FRESH/STALE_ARTIFACT_MS, MODEL_VERSION ->
tools/capability/turn_state.rs; causal_tree config -> config/schema/causal_tree.rs;
decision_engine config -> config/schema/decision_engine.rs;
ApprovalReviewAdapter (+ valid_projection) -> security/approval_ledger/review.rs
(causal_tree/approval_collection.rs deleted); bounded_artifact_summary and the
shared text redactor -> memory/artifact_summary.rs.

R16: decisions.db store, store/, store_schema, store_preparation ->
runtime/decision_journal/ (open_store only; the plane factory-key helper
load_key moves to causal_tree/admin.rs, its only user, so the journal does not
import the engine). OutcomeRecord fields widened pub(super) -> pub(crate).
Test-only open spy counts decisions.db opens.

R17 + 05aS-HOME: decision/loopback.rs and decision/model_pin.rs -> providers/;
decision::telemetry -> observability/causal_event.rs; attempt contract
(sealed, AttemptBinding, DecisionAttemptAccounting, DecisionAttemptReceipt),
DecisionUsage, BackendIdentity and the DecisionError classes ->
cost/decision_attempt.rs (+ error.rs); BackendKind, BackendLocality,
locality_of, configured_kind, configured_selection, ConfiguredSelection ->
config/schema/causal_tree/backend_kind.rs.

Old paths stay as forwarding `use` lines until Part 2.

R18: tests/architecture_boundaries/decision_engine.rs
shared_types_are_not_engine_owned + two planted probes. Scanners retargeted
to the moved paths (decision_backend, causal_domain incl. journal scope,
causal_policy, decision_seals, scanner_probes).

Tests: NEW session_worker::runner::tests::authority::
historical_parent_missing_key_refuses_without_recreation; extended
runtime::ingress_recovery::tests::
ingress_recovery_off_with_causal_tree_disabled_creates_no_key_or_store
(populated legacy database + open spy). Relocated: the exact_calls canonical
test -> causal_tree::state::exact_calls_tests; the credential_ref_for test ->
config::schema::causal_tree::backend_kind::tests.
…ion_engine

R19, R20, R21 (amended by R9-DEAD). Moves only what is still live after 05a.

- R19: causal_tree/branch.rs (CandidateId, CausalCandidate, CandidateSpec,
  Ns8 and their tests; nothing else remained in it) -> decision_engine/candidate.rs.
  ScoringConfig stays in config/schema/causal_tree.rs.
- R20: provider_is_zero_price_exempt users import cost::estimate; the
  runtime::turn_cost re-export is gone. RuntimeCostModel stays live (used by
  the advisory driver and turn_decision/discovery/append.rs), so it moves with
  CostEstimateSource, the test-only CapabilityCostModel/build_cost_model and
  their tests to decision_engine/cost_model.rs; runtime/turn_cost.rs deleted.
  DecisionTokenReservation + DecisionReserveUnavailable -> cost/estimate.rs.
- R21: src/decision/** -> decision_engine/prediction/** (whole backend stack);
  causal_tree/{state(+state/), projection, questions(+questions/),
  candidate_generator, decision_score} -> decision_engine/; the prediction half
  of causal_tree/decision_engine.rs (PredictionPort, prediction_sealed,
  derive_state) -> decision_engine/prediction_port.rs; AdmissionProbe,
  ProbeOutcome, Admission, ProbeReport, GenerationError, AdvisoryProbe and
  admission_probe::tests -> decision_engine/admission_probe.rs;
  HardGatesProbe + legacy_generator_tests -> causal_tree/hard_gates_probe.rs.
  EngineDecision and the rest of the legacy half stay in causal_tree.
- Forwarding: only causal_tree/candidate_generator.rs (re-exports CandidateSet
  for directive/outcome/refusal/decision_engine; declared in causal_tree/mod.rs
  as "forwarding bridge until Part 2"). The 05aS forwards that lived inside
  src/decision (error, loopback, model_pin, telemetry, DecisionKey, KeyDomain,
  budget/factory/backend/protocol/weights item re-exports) and in
  causal_tree/state.rs are removed; every user imports the neutral home.
  git ls-files src/decision is empty.
- Visibility edits only: generator helpers select_family_candidate, from_entry,
  terminal, names_entry, exact_mention, exact_operation_mention,
  contains_bounded are pub(crate) so the legacy generator tests can use them
  from causal_tree::hard_gates_probe; cost_model items pub(super) ->
  pub(crate).
- Retained legacy readers retargeted: agent/terminal/replay.rs,
  tools/delegate_diagnostic.rs, gateway/api/sessions.rs tests.
  runtime/turn_context.rs (R60, TurnRuntimeContext) imports
  decision_engine::state::{ObservedState, RawRoutingInput}; R60 requires those
  fields, the controller handoff names this move.
- Scanners retargeted (none retired): causal_domain (paths, allowlists, AG-01
  and AG-24 scope extended to the moved engine files), causal_policy (AG-03
  scope; providers::loopback allowed), decision_backend, decision_seals,
  labels, panic_policy, scanner_probes.
- Relocated test paths: causal_tree::candidate_generator::tests::* ->
  causal_tree::hard_gates_probe::legacy_generator_tests::*;
  causal_tree::admission_probe::tests::* -> decision_engine::admission_probe::tests::*;
  causal_tree::{state,projection,questions,branch}::* -> decision_engine::{state,
  projection,questions,candidate}::*; decision::* -> decision_engine::prediction::*;
  runtime::turn_cost::tests::* -> decision_engine::cost_model::tests::*.
- NEW test decision_engine::tests::analysis_is_identical_before_and_after_core_move.
…ate turn state

R22: runtime/turn_context.rs::TurnRuntimeContext now also owns the request
parameter capture (capture/prepared_main_parameters), the pinned provider
release catalog, the per-turn recursive allowance (issue/retire), the turn's
one AccountingScope and remaining_g6_tokens (TurnDecisionContext's accounting
field and remaining_g6_tokens are gone), and the pinned TrustedParts that
agent/terminal.rs validate_terminal_scope now reads instead of the last
decision hop. New TurnRuntimeHolder (publication-scoped, shared by every turn)
owns the recovery holder (recovery_scope; the RecoveryScope::prepare of
build_turn_decision_service runs through TurnRuntimeHolder::prepare, the other
four sites stay until Part 2), the recursive allowances, the ordinary child
money, the parent grant, recursive_money and spend_ceiling (SpendCeiling moved
with it). turn_decision/{request_parameters,recursive_budget}.rs deleted;
turn_decision/accounting.rs renamed to opening.rs. Child-scope carriage
(execution_context.child_scope/child_pin and the service's issued child scope)
moved to runtime/child_source/turn_scope.rs. Consumers retargeted:
agent/{turn_engine,terminal,terminal/recovery_commit,send_admission,entry}.rs,
chat/binding.rs, gateway/api/sessions.rs, runtime/{turn_cleanup,
ingress_recovery,turn_terminal}.rs.

R23: count_tokens_with_source, TokenMeasurement, TokenMeasurementSource, the
test-only count_tokens and the #[cfg(test)] token_timing hook moved to
tokens/count.rs (+ tokens/count/token_timing.rs). agent::context_budget and
decision_engine/render.rs import from crate::tokens.

R41: tests/architecture_boundaries/decision_engine.rs::
decision_engine_never_imports_agent_or_tool_execution (crate:: paths, grouped
imports and super:: chains to the crate root; skips #[cfg(test)] items,
tests.rs and *_tests.rs), eight planted probes in scanner_probes.rs.

Tests (written, not run): NEW runtime::turn_context::tests::
turn_context_carries_parameters_catalog_budgets_and_recursive_money_without_decision_service.

R22 acceptance: `accounting:` hits left in src/runtime/turn_decision* are
`decision_accounting::` module paths and `_accounting:` trait parameters of
test backends, not fields. Files that still name TurnDecisionContext or
TurnDecisionService outside runtime/turn_decision* (R26 holders or Part-2
deletions): agent/{embedded_turn,send_admission,terminal,terminal/replay,
turn_request}.rs, agent/turn_tests/{admission,engine_switch}.rs,
causal_tree/{mod,outcome}.rs, channels/{decision_runtime,decision_turn}.rs,
chat/{binding,decision_source}.rs (+ binding tests, persist_restart_tests),
gateway/{api/sessions,decision_runtime}.rs, runtime/{bootstrap,child_turn,
control_ladder,ingress_recovery,operator_spawn,turn_cleanup,turn_publication,
turn_terminal}.rs, runtime/child_source/operator_process/segments.rs,
runtime/ingress_recovery/auth_02_{2b,3}_tests.rs, session_worker/runner/run.rs,
tools/{delegate_diagnostic,surface_owner}.rs.
…R54)

Group 05a4 of the decision-engine strip, Part 1 (spec
REQ-DECISION-ENGINE-STRIP-P1 r3; rulings 05a4-DIR, 05a4-PROJ, R26-HOLDER).

R24 type homes (moves; use/path/visibility edits only)
- runtime/child_source/publication{,/operator,/ordinary_process}.rs:
  ChildExecutionPolicy (built only by of_service), OrdinaryChildPublication,
  OperatorChildPublication, OrdinaryProcessLaunch.
- router/turn_routing.rs: router_provider_binding, commit_training_owned.
- runtime/child_turn/segment_observation.rs: SegmentObservation,
  validate_envelope, initial_observations.
- runtime/turn_terminal/settlement.rs: SettlementIssueToken,
  VerifiedDirectiveApplication, terminal_settlement_ref, settlement_ref.
- runtime/turn_context.rs: TurnOriginMismatch, validate_agent_turn_origin,
  validate_agent_retrieval_provenance, selected_custom_memory_read_scope,
  AgentTurnKind, AgentDecisionError.
- DecisionApprovalView users import security::approval_ledger::pending.

R53 refusal types -> agent/refusal.rs (no causal_tree/engine import);
causal_tree/refusal.rs keeps classify + refine_budget (caller:
agent/terminal/replay.rs).

R54 historical approval -> security/approval_ledger/historical.rs
(historical API, PreparedDecisionResume, DecisionResumeRequest,
AdmittedDecisionResume + gate_evidence, admit_decision_resume,
ResumeEvidenceWitness, ClaimedDecisionResume, ExecutionResumeAdapter,
published_only), parameterised by HistoricalApprovalRuntime and ResumeTurn.
Both baseline ensure!s and claim_decision_grant are in the moved code.

R25 moved methods (body moved once; service methods delegate until Part 2)
- auxiliary_accounting, smart_group_accounting ->
  runtime/decision_accounting/auxiliary.rs; caller channels/decision_turn.rs.
- is_run -> runtime::decision_accounting::is_run; callers
  channels/decision_turn.rs, decision_accounting/auxiliary.rs (+ tests).
- owns_prepared_queues -> runtime::ingress_recovery; callers
  ingress_recovery.rs, turn_cleanup.rs x3.
- reconcile_child_telemetry (+ ChildRecoveryState, child_recovery_gate,
  ChildRecoveryReport) -> runtime/ingress_recovery/child_telemetry.rs;
  caller ingress_recovery.rs.
- record_agent_silence -> runtime/turn_terminal/silence.rs; callers
  agent/terminal.rs x2, agent/turn_engine.rs.
- capability_search_port, capability_search_spec, discovery_entries,
  direct_discovery_ids, prepare/admit_discovered_tool,
  admit_offered_discoveries, appended_execution_specs ->
  tools/capability/search_tool/discovery_admission.rs (TurnSurfacePort).
- execution_specs, runtime_dependency_spec, prompt_skills,
  admit_bound_specs -> tools/capability/execution_binding/specs.rs.
- terminal_execution_trace -> runtime/turn_terminal/execution_close.rs.
  Callers of the discovery/spec/trace rows: agent/{turn_engine,
  embedded_turn,entry,terminal}.rs, agent/terminal/{recovery_commit,
  replay}.rs, channels/decision_turn.rs, chat/{binding,decision_source}.rs,
  gateway/api/{runtime,sessions}.rs, gateway/mod.rs, tools/delegate.rs,
  runtime/child_turn{,/segment_observation}.rs,
  runtime/decision_accounting/auxiliary.rs, runtime/turn_terminal/silence.rs.
- resolve_agent_model, model_can_call_tools, agent_budget_snapshot,
  send_limits, check_live_parity, effective_compaction,
  effective_provider_capabilities, child_scope, credential_for_proof,
  catalog_view, memory/execution/catalog/security/tool_tiering,
  prompt_memory_loader -> runtime/turn_context/publication_scope.rs.
  Callers: resolve_agent_model: channels/decision_turn.rs,
  child_source/publication/operator.rs, child_turn.rs,
  decision_accounting/auxiliary.rs, turn_decision/{discovery,owner_filesystem,
  pending_approval}.rs, turn_publication/operator_process.rs;
  model_can_call_tools: agent/{embedded_turn,terminal,turn_engine}.rs,
  channels/decision_turn.rs, chat/binding.rs, gateway/api/sessions.rs,
  child_turn.rs, session_worker/runner/run.rs,
  tools/capability/{execution_binding/specs,search_tool/discovery_admission}.rs;
  send_limits: agent/send_admission.rs; check_live_parity: chat/run.rs,
  child_source/publication{,/ordinary_process}.rs, turn_publication.rs,
  turn_decision/{discovery,execution}.rs,
  security/approval_ledger/{grant,historical,pending,resolution,resume,review}.rs;
  effective_compaction: chat/{binding,run}.rs, gateway/{mod,api/sessions}.rs;
  effective_provider_capabilities: agent/turn_engine.rs, channels/turn.rs,
  gateway/mod.rs, providers/generation.rs, tools/image.rs;
  prompt_memory_loader: agent/embedded_turn.rs; tool_tiering:
  agent/{embedded_turn,turn_engine}.rs, chat/binding.rs, gateway/api/sessions.rs.
- status()/DecisionPlaneStatus -> runtime/ingress_recovery/status.rs;
  caller runtime/control_ladder.rs.
- validate_silent_terminal -> agent/terminal/binding.rs; callers
  agent/terminal.rs, agent/terminal/replay.rs.
- Child row already lives on the R24 publication types; rejoin_terminal is
  cfg(test) only and stays.

R28 publication rules -> tools/surface_owner/publication.rs
(PublicationRules, PublicationAdmission); TurnRuntimeContext gains
published, discovery, outgoing_delegation. causal_tree/directive.rs deleted
(ruling 05a4-DIR), with TurnDecisionContext::directive,
AgentDecisionError::Directive, HardGates::check_capability_publication,
direct_answer_refs. bind_resume_execution reads the execution-owned resume
surface (fixes every historical resume being refused).

R29 agent/terminal/binding.rs::TerminalBinding; DecisionSettlementRef keeps
decision_id/context/recorded; drafts carry Option<TerminalBinding>, commits
carry TerminalBinding; record_redacted_projection not added (05a4-PROJ).

R27 channels/decision_source.rs -> channels/turn_source.rs,
gateway/decision_source{,/} -> gateway/turn_source{,/}; the cleanup
re-export moves there and gateway/decision_cleanup.rs is deleted.

R26 (ruling R26-HOLDER)
- Builders move to runtime/turn_publication/ and are renamed:
  TurnServiceBuild -> PublicationInputs, TurnLoopRuntime ->
  PublicationLoopRuntime, build_turn_decision_service -> build_publication,
  TurnServiceBuildError -> PublicationBuildError;
  ordinary_process_service / operator_process_service ->
  turn_publication::{ordinary,operator}_process_publication. Callers:
  runtime/turn_publication.rs (publish_scoped, child, ordinary/operator
  process publications) and tests.
- ApprovalRuntime / HistoricalApprovalRuntime impls stay on the service and
  move to turn_publication/approval.rs.
- Resume variant: turn_publication::open_resume_turn (was
  open_issued_resume_turn_context); callers chat/decision_source.rs,
  gateway/api/sessions.rs.
- Pre-turn holders: OperatorSpawnSource::new takes Arc<TurnRuntimeHolder>
  and funds the operator child from TurnRuntimeHolder::
  workspace_remaining_micro_usd (same pinned inputs; caller chat/binding.rs);
  WorkerChildSegments holds the worker publication's holder and refuses a
  segment turn opened from another holder; OperatorProcessSegments::record
  takes the run-keyed approval ledger instead of the service.
- Per-turn objects: TurnDecision carries runtime: &Arc<TurnRuntimeContext>
  (turn_engine reads it and checks it is the decision context's),
  ChildSegmentTurn carries Arc<TurnRuntimeContext>.

Tests
- New (written, not run): runtime::turn_context::tests::
  rehomed_security_items_preserve_refusal; agent::refusal::tests::
  historical_refusal_reply_is_byte_identical; security::approval_ledger::
  tests::historical_resume_drift::
  historical_resume_executes_exact_call_once_and_refuses_drift;
  tools::surface_owner::tests::{publication_rules_match_pre_strip_direct_surface,
  turn_scope_refuses_foreign_owner_and_other_model};
  runtime::child_source::tests::
  child_publication_survives_without_turn_decision_service.
- Moved: runtime/turn_decision/tests/exact_call_approval* ->
  security::approval_ledger::tests::historical_exact_call_approval::*
  (names unchanged).
- Deleted cells (ruling 05a4-DIR; subject was the engine-on refusal path
  removed by R9): agent/agent.rs custom_memory_loader_fixture "unclear
  request" branch of embedded_custom_memory_loader_runs_only_for_selected_retrieval;
  agent/turn_tests/engine_budget.rs configured_small_native_schema_fixture
  `if excluded { expect_err }` cell (DirectiveError::NonExecutableSelection).
  No whole test function is deleted in this group.
- Rewritten: turn_decision::tests::execution::
  child_zero_budget_publishes_no_costed_direct_ref, skill_receipts::
  an_admission_waiting_for_approval_is_not_an_executable_capability_id,
  native_usage admission assertion; directive assertions re-pointed to the
  published surface / is_run (child_process*, g8_segment, worker_probe,
  discovery_surface, skill_receipts, historical_exact_call_approval*,
  catalog_drift, chat/dispatcher/*).

Part 2 deletes: runtime/turn_decision*, channels/decision_{runtime,turn}.rs,
gateway/decision_runtime.rs, causal_tree legacy half.
…rst send, off-path telemetry

P1-01: the analysis no longer prices request/response bytes as tokens. A
configured backend runs inside an allowance of the turn's own accounting
scope: remaining G6 tokens and money minus the protected first send, with the
token allowance shrunk until its highest-rate price fits the money (the same
estimate an attempt reserves). New test
engine_switch::configured_backend_runs_on_a_production_opened_turn opens a
turn through open_turn_context with a valid configuration and proves one
charged physical attempt on the 8,192-token slice.

P1-03: AnalysisAccounting bounds every analysis attempt to the live remaining
budget minus the protected first send (re-read at begin), so an uncertain
attempt that retains its whole reservation never consumes the send's
allowance. The protected send is priced at the largest prompt its compaction
admits plus the response reserve. run_turn drops a brief whose prompt cost
would fail the send's token or money admission.

P1-04: observation no longer awaits the telemetry open. An open store admits
the outcome at once; otherwise a background task opens the store and admits
it. Shutdown drain waits for pending openings. New test
decision_advisory::tests::observation_never_waits_for_the_telemetry_open.

P1-09 (part): RecordingBackend begins and settles a real attempt on the
accounting owner it is given; decision_advisory_runs_without_turn_decision_service
and tight_budget_skips_analysis_and_never_refuses_the_request assert the
exact token and money deltas; the tight-budget test gains the oversized-brief
cell. advisory_decide_mode_projects_prediction_and_lazily_opens_store drains
before asserting the store opened.
…cation and discovery

P1-02 (R51/OV-4): in Full the backend invocation runs at Full whatever the
child manifest level (tools/execution.rs); a child's runtime dependencies are
not limited by manifest membership (execution_binding/specs.rs); a Supervised
manifest no longer makes a Full turn's effectful discoveries
ApprovalRequired (surface_owner/publication.rs). Integrity, budget, depth and
plan-mode checks are unchanged; Supervised and ReadOnly are unchanged.

Tests: full_autonomy_has_no_permission_limits gains a real native-tool cell
(FileReadTool reads a file outside the workspace from a Full child with a
Supervised manifest); new
surface_owner::tests::full_turn_discovery_ignores_an_attenuated_child_manifest
(Full: effectful member and non-member Passed; Supervised: member
ApprovalRequired, non-member blocked). ChildCase::build_with registers extra
tools.
…run the advisory driver

P1-05 (R9/R26): DecisionAdvisory::for_publication builds the advisory
driver of a publication: an enabled engine gets a factory from the effective
engine backend with the publication's state key; a disabled engine builds
none (P1-I2). The root builder, ordinary and operator child publications
(runtime/child_source/publication{,/operator}.rs) and the ordinary and
operator worker publications (runtime/turn_publication/{ordinary,operator}_process.rs)
use it. Worker segment turns (session_worker/runner/run.rs), delegated child
turns (tools/delegate.rs) and sessions_spawn task turns
(tools/sessions_spawn/task_mode.rs) analyse on their own TurnRuntimeContext
and accounting scope instead of hardcoding no advice.

Test: runtime::child_source::tests::child_publication_builds_the_advisory_driver_only_when_enabled.
P1-06: token_count reads the token counter and its timing hook at their R23
home (src/tokens/count.rs, src/tokens/count/token_timing.rs);
decision_backend expects the borrowed constructor transport field in its
new home (src/runtime/turn_publication/builder.rs); the catch_unwind
inventory drops its six obsolete entries (agent/turn_tests/admission.rs and
five deleted turn_decision test files); causal_domain drops the deleted
causal_tree/directive.rs allowlist. R8: the engine boundary's
module-resolving path scanner moves into helpers.rs as
module_resolved_paths (with module_segments), so crate_rooted_paths has one
definition and every helper lives in helpers.rs; R41 coverage is unchanged.
…te helpers

P1-07 (R9-DEAD): PredictedState, ForeignDecision and
CausalState::record_fingerprint were production items before the strip and
are dead after it. They are deleted, not hidden: the CausalState.predicted
field, the record branch of the canonical state JSON (CanonicalRecord, the
record flag of canonical/serialize) and, by the compiler's cascade,
ObservedStep's event id (field, accessor, constructor argument) and
segment_observation::project_trace's event argument.

Deleted exclusive tests: state::turn_state_tests::backend_cannot_set_trusted_state
(with its sealed/validated helpers). Record halves removed from
lineage_and_time_are_not_material, the shuffled-order fingerprint test,
canonical_tests (oracle, allocation, empty-state golden, sequence order,
model admission), exact_calls_tests (renamed
absent_observations_keep_legacy_canonical_and_present_facts_bind_the_fingerprint)
and child_process::g8_segment. Their material-fingerprint assertions are
unchanged.
…erties (part)

P1-09 (fixable part, first half):
- RecordingBackend answers every real compiled question through
  scripted_answers, predicting a chosen action with full confidence (it no
  longer depends on the native-decision fixture's model pin).
- engine_switch::high_confidence_refuse_or_clarify_advice_never_changes_the_turn
  (new): a backend that predicts refuse or clarify with full confidence
  through the advisory driver (asserted on the analysed next_action) yields
  the same single provider request, tools, dispatches and reply as the
  engine-off turn. The retired commit_threshold cell is removed from
  engine_on_and_off_open_identical_turns.
- engine_on_and_off_open_identical_turns now also compares the opened turns'
  execution refs and published surface. Historical resume is waived (owner
  rule), so it opens no resume turn and says so.
- runtime::turn_context::tests::child_turn_context_funds_recursive_money_only_through_the_parent_grant
  (new, R22 parent-grant cell).
…ol set; R29 operator cells

The operator child's frozen capability set is a membership limit (controller
ruling on audit-fix pass 1). In Full the operator task service publishes under
the parent surface policy and a restored operator worker keeps its actual
surface policy (R51/OV-4). Supervised and ReadOnly still publish exactly the
frozen set. Parity, budget and per-call policy are unchanged.

Tests: operator_case builds the task service through the production path
(claim, issue_task, task_service) and the first segment's execution context.
Supervised: the frozen set excludes an ACL-denied probe; it is not published
and its dispatch is refused with 0 calls. Full: no frozen allowed_tools, the
probe is published and executes once. In both levels an exhausted task or
parent allowance funds no child share, and executor drift refuses the source,
the authority and a new task service with no dispatch.
… and gate tests

P1-09: the four discovery tests deleted with the hop exercised live
behaviour. They are restored on a prediction-free opening: each turn asserts
is_run and zero prediction-port calls, and discovery happens only through the
model's capability_search call.

- discovered_tools_are_capped_per_turn_and_admitted_in_search_rank_order:
  the rank-order and cap-of-one assertions are unchanged. The production cap
  of ten is now reached by real searches and admissions (eight extra peers)
  instead of seeded turn state; the eleventh is refused and passes once the
  cap is raised.
- search_hits_are_admitted_for_the_next_request_through_the_same_gates
- non_english_discovery_reaches_admitted_family_without_full_registry
  (the Plan cell uses recorded_approval_source in Plan mode)
- production_discovery_selects_a_skill_and_loads_its_body_only_on_execution
…rify cells run a configured loopback backend

The configured-backend and Refuse/Clarify cells configured BackendKind::Mock,
which publication's from_config refuses (MockNotConfigurable), so they failed
during setup. They now configure a fixed compatible-HTTP backend at a bounded
loopback endpoint the test owns (ScriptedDecisionEndpoint: capped bodies,
bounded shutdown, scripted full-confidence answers). Publication builds it
through from_config, and the cells analyse with the publication's own advisory
driver. The default AdmittedTurn transport builds loopback backends with the
production HTTP transport and still refuses everything else.

Both cells keep the attempt-charge assertions (one request, 1,200 tokens and
a cost charged to the turn's scope) and the Refuse/Clarify cell keeps the
full-confidence prediction assertions.
…ws prediction-free publication

With direct_shell=true at Full, publication's direct surface makes both
file_read and shell core, so the cell now asserts both core tools and that the
core shell offer admits nothing new. The read-only cell, exclusion,
idempotence, rank and cap assertions are unchanged.
…ion with one opener

Before the telemetry store is open, every outcome spawned its own task that
held the payload, and a growing retain scan tracked the handles, so a stalled
opening allowed unbounded tasks and payloads. Now the first observation starts
exactly one opener task and a non-blocking queue with the ObserveQueue bound
(observe_queue). An outcome that does not fit is dropped and counted. The
opener publishes the store, takes the queue over under the admission lock,
forwards the queued outcomes and adds the pre-open drops to the store's
existing drop counter. Drain waits for the single opener. The retain scan is
gone.

New test stalled_opener_leaves_a_bounded_queue_and_counts_the_overflow: on a
current-thread runtime with a stalled opener, 11 observations at bound 4 each
complete on their first poll and leave a queue of exactly 4 with 7 drops
counted. After drain, the store holds 4 outcomes and ObserveCounters.dropped
is 7.
…tch commits

Rebased group by group onto origin/main 6676a46 (no squash). Conflicts
were resolved in the 05a and 05a4 commits; this commit carries the one
path retarget the resolution needed afterwards.

Kept from the switch: the confirms_plain_answer acceptance of
unbound+denied attempts (and its cells), the TurnScheduler requeue fix,
the quinn-udp bump, and control tools on plain answers (Part 1 has no
abstention gate left to withhold them).

Removed with the prediction hop: clarify_degraded_directive,
admitted_fallback_answer, is_plain_answer_abstention, the drift-path
degrade, and the test-only plain_abstention_fixture module.

Tests deleted (subject gone with the hop):
- runtime::turn_decision::tests::agent_decision::clarify_degrades_only_with_a_gate_passed_answer
- main's rewrites of catalog_drift_closes_with_fresh_unlinked_terminal_and_no_dispatch
  and cancellation_during_backend_and_catalog_drift_wins_before_recovery
  (already deleted by 05a)
- main's enabled cell of engine_switch::run_plain_fallback_cell
  (enabled_builtin_low_confidence_run_turn_publishes_plain_answer, already
  deleted by 05a)
- main's "unclear request" Clarify-degrade cell in
  agent::tests::custom_memory_loader_fixture (already removed by 05a4)

Tests rewritten to the post-R9 contract:
- terminal_scope LinkedFixture: the drift turn settles as Answer on the
  "none" target with no execution refs, and the terminal carries real
  serving evidence (replaces the stale Clarify expectation)
- tools::delegate::tests::delegate_child_logical_deadline_still_refuses_provider_calls
  -> delegate_child_logical_deadline_never_refuses_the_child: a spent
  logical deadline never refuses the child; the allowed tool executes once

Also: main's refused-call outcome cell builds RecordedToolCall through
runtime::decision_journal::store instead of the causal_tree::store
forwarding path.
… layout

Real: seven out-of-line test files added by the strip lacked the
repository's `#![cfg(test)]` marker, so AG-01, AG-28, process, ingress,
provider and config scanners read their test code as production. Mark them.

Stale, retargeted with the same coverage:
- compaction: the budget producer moved to turn_context/publication_scope;
  pin the turn_decision forwarder exactly and add a mutation proof for it.
- decision_seals: salted_question_ids path; option-id allowlist reduced to
  the four criteria keys left after forced choices were deleted (R9-DEAD).
- loop_prompt: reviewed TurnDecision handoffs carry the R26 runtime field.
- providers: auxiliary accounting moved to runtime/decision_accounting.
- causal_domain: per-file entries for hard_gates_probe (CatalogView, R21)
  and refusal (agent::refusal reasons, R53).
- decision_seals AG-28: decision_advisory::build_state may name
  TrustedState::from_runtime once (R9); literals and writes stay refused.

Scanner bugs in the new R41 engine-import guard (never run before):
path_tree_end treated a block `{` as a use group and panicked; and
whitespace removal turned `use crate::` into `usecrate::`, so five planted
use-statement probes were not caught. Groups now open only after `::`, and
compact_rust_path_text keeps a space between identifiers.

No probe deleted; no scanner retired.
- const fn: TurnDecisionService::advisory, terminal_outcome_action,
  TurnDecisionContext::runtime, plane_status, prepared_tool_name,
  DecisionAdvisory::factory.
- DecisionAdvisory::observe is synchronous (it never awaited and must not
  block); callers drop `.await`, the stalled-opener test drops now_or_never.
- child_telemetry: bind the recovery gate before `if let`.
- hard_gates_probe: return HardGates::apply directly.
- tests: map_or_else, assert_eq against empty values, keys() loop, no
  redundant clone, no drop of borrowed views, queues asserted alive.
Root causes found by the first test run of Part 1:

- Full-autonomy children (R51 bypasses manifest membership) published the
  parent's Direct delegator without a sealed outgoing manifest, so binding
  failed with "answer surface has no sealed child manifest". A child now
  publishes a delegator only with its sealed outgoing manifest (V-16
  structural integrity, not a permission limit).
- The same bypass exposed the parent's catalog to a child's discovery while
  the child's own task registry carries no search backend: every such
  child failed with "capability discovery unavailable". A child without a
  live search control now simply has none.
- Without the retired child hop, a child published only Direct entries, so
  the tools its parent chose (its sealed manifest) were refused as "not
  exposed". The manifest's members are now published like Direct entries,
  under the same publication rules.
- Prompt skills listed only discoverable skills: a published skill was
  neither advertised nor produced a selection receipt. Published skills are
  now listed (no loader needed) together with discoverable ones.
- R55: `prx causal config` / Gateway backend mutations wrote the legacy
  `[causal_tree.backend]` while the engine builds from the migrated
  `[decision_engine.backend]`, so a backend change never reached the engine
  and failed validation. Mutations start from and write the effective
  backend (both tables kept identical); credential reference, pairing,
  calibration and the validator's change detection read it too.
- docs/configuration.md no longer describes the removed engine-on Clarify
  degrade.
- decision_engine::admission_probe::tests: the NamedTool fixture schema
  `{"type":"object"}` has no `properties`, which the catalog's
  SchemaCleanr::validate correctly refuses (`Err(Schema)`); it now declares
  `properties: {}`. advisory_ordering_and_limits_match_structural_order
  compares candidates by action and capability ids: each fixture run mints
  its own key, so keyed namespaces never match across runs.
- config::secret_paths: the R15/R17 schema homes causal_tree.rs,
  causal_tree/backend_kind.rs and decision_engine.rs join the secret-alias
  scan.
- decision_engine::prediction::factory::tests::disabled_builds_builtin_only_order_and_no_transport:
  the default engine switch is off (R55), so the default row is Disabled.
- decision_engine::prediction::factory::tests::a_disabled_plane_creates_no_key:
  disables the plane (`[causal_tree].enabled`), which owns the key.
- config::schema::decision_engine_config_tests::explicit_empty_engine_backend_uses_defaults_not_legacy:
  the `auto` default plans its unconfigured loopback probe; no legacy slot.
- causal_tree::admin::tests::an_excluded_configured_backend_is_not_tested and
  cdp_cli::a_disabled_plane_never_opens_the_credential_store: the engine
  switch (R55), not `[causal_tree].enabled`, gates the factory.
…imits (R51)

Tests that asserted an identity-scope ACL deny at the default Full level now
run their ACL cell below Full (Supervised), where the deny still applies; a
Full cell asserts the R51 Allow where the test's subject is the level
itself. No test deleted.

Rewritten tests:
- agent::execution_policy::tests::configured_cli_policy_controls_tool_service_and_preserves_actor_scope (Supervised)
- causal_tree::gates::tests::{owner_check_and_execution_scope_use_distinct_source_identities, mcp_alias_acl_denial_uses_executable_public_name} (Supervised)
- tools::execution::tests::admission_matrix::policy::policy_deny_refused_without_backend_call (Supervised)
- tools::execution::local_operator_scope_tests::a_scheduled_scope_carries_the_configured_policy_for_its_creator (Full ⇒ Allow; Supervised ⇒ scope Deny)
- cron::scheduler::tests::a_scheduled_run_is_held_to_its_creators_scope_rules (Full rows run; Supervised webhook row refused)
- int_p1_cross_module::int_cs_03_decide_layers_scope_and_autonomy (Full ⇒ Allow; Supervised ⇒ Deny)
- gateway::api::memory::tests::memory_routes_enforce_service_scope_policy_and_audit_denials (Supervised)
- gateway::api::skills_lifecycle::tests::lifecycle_routes_enforce_service_scope_policy_and_audit_denials (Supervised)
- gateway::compat::tests::card_jwt::mcp_tool_call_denied_by_policy_returns_the_schema_rejection_structure (Supervised)
- tools::skills::executable_tests::an_undeclared_or_ungranted_tool_is_refused (Supervised)
- agent::entry::tests::cli_agent_configured_scope_denies_wire_file_write (Supervised)
- tools::execution::tests::full_autonomy_has_no_permission_limits: the
  explicit-grant cell's assertion now reports the outcome text.
…ediction hop

Subjects that are still live are rewritten to the prediction-free contract
(the model chooses tools; the engine only adds a brief). No test deleted.

Fixtures:
- native_decision_fixture: removed the hop-only `require_request` and
  child-delegation request assertions (and the dead parameter at all 41
  call sites); a request that does arrive (advisory analysis) must still
  get a real response, falling back to the plain Answer when its scripted
  candidate is absent.
- tests/support/{answer_agent,native_agent,selected_memory}.rs: rewritten
  to prediction-free harnesses (no decision endpoint, no prediction-binding
  validation); native_agent still checks the provider receives the
  published tool; selected_memory seeds the fact before the turn instead of
  inside the decision request.
- with_worker_probe_manifest: dead `generation_negative` parameter removed.
- admission fixture: no longer forces the engine on for ordinary
  controlled turns (the caller's switch is honoured).

Rewritten tests:
- tools::delegate fixture gate (12 tests): Delegate selection ⇒ the parent
  publishes `delegate` with a sealed outgoing manifest.
- channels::tests::signal_accounted::assert_decision_path (5 tests): one
  prediction-free Answer row; no retrieve/discover/refuse rows.
- chat::dispatcher::turn_characterization_tests step 7.1 (3 tests):
  prediction-free opening on the enabled plane, Answer committed.
- security::approval_ledger historical_exact_call_approval (6 tests incl.
  native_usage): the backend is asked 0 times at opening.
- chat::decision_source::advisory_decide_mode_projects_prediction_and_lazily_opens_store,
  agent::turn_tests::engine_switch::{configured_backend_runs_on_a_production_opened_turn,
  high_confidence_refuse_or_clarify_advice_never_changes_the_turn}: the
  turn opens under a cleanup owner, as production entries do, so it has its
  accounting scope.
- engine_switch::engine_on_and_off_open_identical_turns: the brief block is
  excised, not truncated at its marker.
- agent::entry::tests::cli_agent_persists_no_secret_from_a_{provider_error,reply}:
  the recorded backend is `none` (prediction-free).
- agent::terminal::tests::unissued_entrypoint_label_drafts_are_rejected_before_side_effects,
  runtime::turn_decision::tests::recorded_chain::enabled_agent_service_records_only_a_successful_decision_chain:
  R29 refuses for the missing terminal binding first.
- tools::sessions_spawn::tests::kill_and_events::spawn_denied_without_grant_under_supervised:
  refused at the approval gate (the surface publishes, execution approves).
- runtime::child_source::tests::child_publication_survives_without_turn_decision_service:
  the ordinary publication re-seals the pinned refs under its run-scoped
  parent authority (refs, parent refs and depth compared).
…ry admission, deferred child members

Controller rulings on PR #204 CI round 2, applied in production code:

- Ruling 1: loading a managed skill through skill_read records the skill
  selection receipt (idempotent per revision), beside the hydration
  receipt. A failed selection write marks the task's evidence dropped.
- Ruling 2: paid auxiliary calls (Signal vision, smart-group classifier)
  first run the unpriced-model refusal of provider send admission
  (send_admission::protected_first_send), so a turn whose main send will
  be refused never pays for an auxiliary call.
- Ruling 3: an auxiliary call reserves at most the live remaining budget
  minus the protected first send (G6 answer tokens and the price of the
  largest admissible prompt plus its response reserve, P1-03). A failed
  or uncertain classifier that retains its whole reservation no longer
  starves the main model. AccountingScope::begin_physical_within is now
  visible to runtime::auxiliary_call; the two AuxiliaryAccounting test
  constructors pass a zero protection.

Also a defect found by tool_search_roots (R28): a child that owns a live
capability_search control published every Searchable sealed-manifest
member eagerly ("deferred memory reader was eagerly declared"). A new
admission fact, child_discovery, records whether the child's own
executor carries the discovery control; with it, deferred members stay
behind capability_search; without it they are still published.

Fixture updates that ride with these types: AdmissionFactsV1 fixtures
set child_discovery; decision_advisory_runs_without_turn_decision_service
runs each backend model in its own workspace (the fixed fixture scope id
made both models share one turn's money limit in one cost journal).
Removed an unused cfg(test) import in turn_decision.rs.
…p, R51 and stale fixtures

Deleted (subject was only the deleted hop or waived historical resume):
- security::approval_ledger::tests::historical_resume_drift::historical_resume_executes_exact_call_once_and_refuses_drift
  (ruling 5; module file removed)
- runtime::turn_decision::tests::discovery_surface::named_skill_and_loader_select_the_skill_without_competing_targets
  (hop skill selection; skill discovery is covered by
  production_discovery_selects_a_skill_and_loads_its_body_only_on_execution)

Rewritten to the prediction-free contract (R9, R28):
- agent::entry::tests::{cli_single_shot,cli_repl}_routes_*, cron_job_turn_routes_*,
  xin_task_turn_routes_*: recalled memory publishes nothing; no extended tool refreshed
- agent::turn_engine::tests::execution_grounding::* (11): opening commits no tool
- agent::turn_engine::tests::{run_tool_call_loop_native_mode_persists_each_tool_output_once,
  compiled_request_event_accounts_for_prompt_and_tool_payload_sizes,
  compiled_request_event_breaks_the_tool_surface_down_by_root_and_adapter,
  a_capability_search_hit_is_declared_on_the_next_request_and_not_the_current_one,
  compiled_tool_snapshot_is_rendered_once_per_turn_with_a_stable_system_prefix}:
  fixture tool configured Direct (tool_tiering.always_include)
- engine_surface::{run_tool_call_loop_persists_large_tool_output_documents,
  production_turn_far_longer_than_the_idle_window_survives_on_progress,
  production_turn_whose_tool_goes_silent_past_the_window_is_terminated}: Direct fixture tool
- engine_surface::tool_loop_refreshes_only_intent_selected_dynamic_capabilities
  -> tool_loop_refreshes_only_published_dynamic_capabilities;
  prompt_guided_provider_receives_only_the_compiled_turn_tool_snapshot,
  tool_loop_rejects_a_registered_tool_not_exposed_for_the_turn,
  turn_exposure_guard_preserves_unknown_tool_diagnostics: configured Direct pdf_probe
- agent::agent::tests::{embedded_custom_memory_loader_with_durable_builtin_diagnostics,
  embedded_selected_memory_preserves_original_source_input}: one Answer receipt, no prior retrieval
- agent::agent::tests::bounded_native_fixture_preserves_supervised_and_readonly_denials:
  the gate refuses the call itself and the model answers after it
- channels::tests::process_message (factory_reload_fixture): one Answer row per input
- chat::binding::tests::shell_parity_working_root::chat_workdir_reaches_real_worker_and_unset_keeps_worker_workspace:
  binds the published sessions_spawn definition
- runtime::turn_decision::tests::catalog_drift::enabled_service_allocation_diagnostic_holds_candidate_count_fixed:
  hop-only Discover/UseTool candidate shape assertions removed
- child_process::full_policy_issues_real_parent_delegate_grant: hop candidate re-gate removed
- child_process::supervised_without_exact_approval_cannot_issue_parent_delegate_grant:
  Supervised publishes the Direct spawn; the opening grant is Supervised, model-only, owner-bound
- child_process::g8_segment::*: outside-manifest call in a Full child is refused by its
  call budget (R51); no prediction at opening
- discovery_surface::named_memory_recall_becomes_ready_after_selected_discovery_without_publishing_peers
  -> named_memory_recall_is_reached_by_search_without_publishing_peers
- discovery_surface::selected_native_write_can_discover_read_peer_without_offering_shell
  -> direct_native_write_can_discover_read_peers_through_search
- skill_receipts::below_bar_skill_execution_adapter_is_not_published
  -> direct_skill_execution_adapter_is_published_without_a_prompt_document
- skill_receipts::rejoin_without_provider_facts_joins_the_terminal_once_as_unknown
  -> rejoin_of_a_verified_recovered_completion_joins_the_terminal_once
- runtime::child_source::tests::child_publication_survives_without_turn_decision_service:
  task-scope check covers the sealed manifest only; child dispatch envelope carries its owner
- enabled_service::enabled_service_with_unavailable_key_does_not_open_a_writable_store: R55 engine switch
- execution::child_zero_budget_publishes_no_costed_direct_ref: child with a sealed outgoing manifest
- recorded_chain::enabled_agent_service_records_only_a_successful_decision_chain:
  drop the five synthetic latency rows of the deleted reader
- tools::execution::tests::full_autonomy_has_no_permission_limits: H2 cell uses
  lifecycle_experiment_admit with an admitted descriptor
- tools::shell_parity_tests::full_parent_supervised_child_keeps_native_restrictions_after_approval
  -> full_parent_attenuated_child_runs_native_tools_at_full (R51)
- tests/agent_e2e.rs e2e_memory_enrichment_injects_context, e2e_multi_turn_with_memory_enrichment:
  a warm-up turn opens the Agent's own session; the fact is seeded there
Removed the now unused AdmittedTurn::with_prediction_tools.
…ts own executor serves

Root cause of the g8 child failures ("selected executable capability is
missing"): a child's catalog is its parent's pinned catalog, and in Full
the R51 bypass dropped manifest membership from publication, so the child
published every Direct entry of the parent catalog, including ones its own
executor does not run (the parent's interactive-only chat_schedule in a
process worker). execution_specs then failed closed on the missing
descriptor and the whole child turn failed. The "has no call budget"
denial the round-3 notes attributed to a per-tool budget is the R6b
refusal of a model-turn context without any call budget (the raw adapter
context before segment admission). It applies to every tool. The call
budget is not per tool, so in Full it already funds every tool the child
can see (ruling 1).

Fix: a new admission fact, child_served, records for a Full child turn the
pinned catalog entries its own executor serves with descriptor parity.
Outside its manifest, a Full child publishes (direct_surface) and
discovers (child_membership_admits) only those entries. Manifest members,
Supervised/ReadOnly children and non-child turns are unchanged. Money
ceilings, recursive money and spawn depth are untouched.

AdmissionFactsV1 fixtures set child_served: None.
… round, lifecycle selection gates

- g8_segment (both cases): the unbudgeted adapter context is refused for
  every tool, in or outside the manifest (R6b). After segment admission,
  the Full child publishes and binds the served capability outside its
  manifest and runs it under its call budget (R51). It never publishes or
  binds the parent's chat_schedule, which its executor does not serve.
- child_source operator cells: dispatch through the production path
  (ChildSegmentTurn::admit stamps the call budget, bind_agent_execution,
  turn surface) instead of the raw segment context. Full runs the probe
  once; Supervised refuses it.
- delegate agentic_mode_inherits_parent_tools_only_with_explicit_wildcard
  (ruling 2, R28): the child searches with capability_search, then calls
  the inherited deferred echo_tool, then finishes (3 requests). The test
  asserts echo_tool is not declared before the search.
- skills lifecycle acceptance (ruling 4): the slowness was new. Since round 3,
  skill_read records the selection receipt synchronously, so the
  selection_gate that held the run's FIRST chat request waited for a
  receipt only that request's answer can produce. Each gated task stalled
  20 s on the gate cap (its panic is swallowed in spawn_blocking).
  The gates now hold the request carrying the skill_read result
  (run_task_selected, scenario_b, scenario_e). The acceptance module
  runs in 21 s, so no nextest override is needed and no scenario is
  weakened.
Move the credential_containment_tests module (C0-C provider/credential
containment) unchanged into src/tools/delegate/credential_containment_tests.rs,
declared as `#[cfg(test)] mod credential_containment_tests;`. No behaviour
change: the module body is moved verbatim (dedented one level) and keeps
its `super::*` imports.

src/tools/delegate.rs: 5015 -> 4252 lines (source_size_gate limit 5000).
No architecture-scanner path names the moved module: the delegate.rs entries
in tool_tables, compaction, loop_prompt, loop_contracts, labels and
panic_policy name production code that stays in delegate.rs, and the moved
code has no catch_unwind or log lines.
@g1e2x87
g1e2x87 merged commit f4cc5ad into main Nov 14, 2026
32 checks passed
@g1e2x87
g1e2x87 deleted the feat/decision-engine-strip-p1 branch November 14, 2026 01:29
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant