Resolve the default compaction policy from the bound task
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
StockExecutorandHarnessBuilderholdOption<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.
StockExecutionholdscompaction: 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_differentialdiffered from--all-featuresby 32 bytes. An allocation trace shows exactly one allocation differs: the boxBoundedContentFuture::newmakes forSourceStage's projection future (472 vs 440 bytes).- That future holds the source's
BoundedContentFutureacross its await. Inside it is aNativeAllocationāNativeMaterializationWork, which carries a field gated onacyclic-fs/native-mount. - The future is now named:
context::source::project_sourceis a plainfnreturning the sameasync moveblock, so its upvar layout and size are unchanged. Anasync fnwas measured at +136 bytes and rejected. source_projection_future_bytes()(cfg(test)) takessize_ofof that function's output type without calling it.preparation_expected.jsonstores 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 largeblock_onfutures no longer enters the measurement.
- That future holds the source's
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 --checkon 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_ofof the one allocation whose type varies by cfg.
- Compaction default. It was decided in two places. Now
- Proof:
- Default branches. The bundle test covers both.
filesystem::task_runtime::tests::canonical_compactioncovers the private-volume threshold branch.compaction_policy_matrix_resolves_through_the_task_writerruns every writer Ć override Ć session combination through the real resolver. - Single resolution.
compaction_resolves_once_with_original_workchecks exactly one writer resolution on the original ledger. Negative control:repeated_resolution_negative_control_is_detectedrestores 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_allocationspasses 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 hardcoded472fails withactual=[178,48,73,24148,4695,8728], expected=[178,48,73,24180,4695,8728]. A mutant using anasync fnwas caught by the exact table (+136 bytes) before it was rejected.
- Default branches. The bundle test covers both.
- Entry points:
- Every
StockExecutionconstruction (ordinary, native context, native stock composition, summary execution, native session) reads the policy and writer only throughcompaction()/compaction_policy(). - Policies arrive from
HarnessBuilder, the filesystem task runtime,local::bind,task_commands::modelandStockExecutor::new. SourceStage::applyis the only constructor of the projection future.
- Every
- Gaps: none.
Issues found while verifying this PR (separate root causes, not changed here)
- Main's lib tests don't compile with
--features filesystemalone (7c05f9cdc).runtime/tools.rs:216is gatedcfg(all(test, not(wasm32)))but reads thehooks-gatedcrate::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 withfilesystem,hooks. --features native-executionalone fails to compile with amounts_muterror onMountTable. This exists without this PR.executor::json::differential_properties::exact::encoding_has_exact_production_work_physical_allocations_and_original_custodyfailed under--features filesystemat 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.