Resolve the default compaction policy from the bound task

Closed
VA
2d ago
+414-63

Problem

bundle::tests::builder_rejects_missing_or_conflicting_bindings fails on main in the gate run cargo test --locked -p acyclic-harness --all-features --no-fail-fast (baseline 66ddfd542). It fails the same way under harness-only features and with acyclic-harness-providers, so feature unification is ruled out.

Breaking commit: #898 (b66d74738). It rejects threshold compaction at turn entry unless the task can publish private summaries. It switched only filesystem/task_runtime/execution.rs to Disabled where the task cannot. Every other stock composition, HarnessBuilder among them, still defaulted to Threshold. Those turns now fail with Unauthorized("summaries require the task's private volume") before any model work.

Why the gate missed it: PR CI runs no harness Rust test target, so this lib test never ran before merge.

Fix

  • StockExecutor and HarnessBuilder hold Option<CompactionPolicy>.
  • When no policy is set, StockExecution::compaction_policy() resolves it from the bound task: threshold where the task has a private summary writer, none elsewhere. That method is the single place the default is decided.
  • An explicit policy is unchanged and still verified at turn entry.
  • The task runtime's own special case is removed, because the executor now owns the default.

Review round: single resolution and exact accounting

  • One compaction resolution per turn. StockExecution holds compaction: OnceLock<ResolvedCompaction>, which is the policy plus the task's private summary writer. compaction() resolves it on first use. The entry check (binding::verify_summary_destination), the policy read and the summary path (executor/summary.rs) all read that one value, so the writer is no longer re-resolved three times on the original native IO.
  • The exact preparation table no longer depends on features. Under --features filesystem, preparation_differential differed from --all-features by 32 bytes. An allocation trace shows exactly one allocation differs: the box BoundedContentFuture::new makes for SourceStage's projection future (472 vs 440 bytes).
    • That future holds the source's BoundedContentFuture across its await. Inside it is a NativeAllocation → NativeMaterializationWork, which carries a field gated on acyclic-fs/native-mount.
    • The future is now named: context::source::project_source is a plain fn returning the same async move block, so its upvar layout and size are unchanged. An async fn was measured at +136 bytes and rejected.
    • source_projection_future_bytes() (cfg(test)) takes size_of of that function's output type without calling it. preparation_expected.json stores the feature-independent base, and the test adds the derived term. Every comparison stays exact.
    • The test future is pinned on the stack (std::pin::pin!), so tokio's boxing of large block_on futures no longer enters the measurement.

Tests

  • bb1, merged tip 44f024e83 (main 7c05f9cdc merged in): --lib -- executor:: bundle::
    • --all-features: 108 passed, 0 failed.
    • --features filesystem,hooks: 108 passed, 0 failed.
  • bb1, pre-merge commit 96dd6aac6, --features filesystem --lib executor::: all preparation, compaction and bundle tests pass. The one failure there is listed under the issues found while verifying this PR.
  • Formatting: rustfmt --check on every file this PR touches is clean.

Guarantee

  • Class: M: parallel mechanisms, in three places, each replaced by one derivation point.
    • Compaction default. It was decided in two places. Now StockExecution::compaction_policy() derives it from the bound task, and the old default is no longer representable.
    • Writer resolution. It was re-run per consumer. Now one OnceLock<ResolvedCompaction> serves the entry check, the policy read and the summary path.
    • Allocation table. It hardcoded values for one feature set. Now it holds a feature-independent base plus size_of of the one allocation whose type varies by cfg.
  • Proof:
    • Default branches. The bundle test covers both. filesystem::task_runtime::tests::canonical_compaction covers the private-volume threshold branch. compaction_policy_matrix_resolves_through_the_task_writer runs every writer Ɨ override Ɨ session combination through the real resolver.
    • Single resolution. compaction_resolves_once_with_original_work checks exactly one writer resolution on the original ledger. Negative control: repeated_resolution_negative_control_is_detected restores the pre-change repeats and asserts both detectors trip (allocations and ledger receipt). It runs in the suite.
    • Exact table. request_preparation_has_exact_physical_allocations passes under --all-features, --features filesystem (96dd6aac6) and --features filesystem,hooks (merged tip). Negative control, actually run under --features filesystem,hooks: replacing the derived term with the old hardcoded 472 fails with actual=[178,48,73,24148,4695,8728], expected=[178,48,73,24180,4695,8728]. A mutant using an async fn was caught by the exact table (+136 bytes) before it was rejected.
  • Entry points:
    • Every StockExecution construction (ordinary, native context, native stock composition, summary execution, native session) reads the policy and writer only through compaction() / compaction_policy().
    • Policies arrive from HarnessBuilder, the filesystem task runtime, local::bind, task_commands::model and StockExecutor::new.
    • SourceStage::apply is the only constructor of the projection future.
  • Gaps: none.

Issues found while verifying this PR (separate root causes, not changed here)

  • Main's lib tests don't compile with --features filesystem alone (7c05f9cdc). runtime/tools.rs:216 is gated cfg(all(test, not(wasm32))) but reads the hooks-gated crate::hooks::mcp_identity_tests, which fails with E0433. Introduced by #982 (32a9ae569) and reported to the coordinator. This is why the merged tip was verified with filesystem,hooks.
  • --features native-execution alone fails to compile with a mounts_mut error on MountTable. This exists without this PR.
  • executor::json::differential_properties::exact::encoding_has_exact_production_work_physical_allocations_and_original_custody failed under --features filesystem at base 66ddfd542 (ledger/trace differs). It passes on the merged tip after #998.

šŸ¤– Generated with Claude Code


Summary by cubic

Fixes the default compaction policy so compositions without a private summary volume no longer fail before model work.

The default is now resolved from the bound task at the single point StockExecution::compaction(): threshold where the task has a private summary writer, disabled elsewhere. StockExecutor and HarnessBuilder hold Option<CompactionPolicy> instead of defaulting to threshold, and the task runtime's own special case is removed. The resolution is cached behind a OnceLock per turn, so the summary destination, binding verification, and summary path share one writer resolution instead of redoing it.

  • An explicitly set policy still behaves unchanged and is verified at turn entry.
  • The bundle test now covers the default disabled branch and the explicitly armed rejection branch.
  • The exact preparation allocation table is now feature-independent: the one feature-variable allocation, the boxed source projection, is added from its type's size_of.
  • New tests cover the policy matrix through the real resolver, single resolution, and a negative control for repeated resolution.