Avoid redundant blob publication indexing and restore capture pins
Blob publication no longer builds an identity table for a batch whose object kinds already prove that its IDs are distinct. A one-chunk blob publishes a BlobChunk and a Blob page; those can never be equal ObjectIds. This removes #961's extra allocation and collision-dependent probes instead of accepting them into the performance expectations.
The original allocation and item pins are restored byte-for-byte: Linux owner capture 46 allocations; complete Linux rescan 331 allocations and 331 normalized items. Windows retains its original 43 / 324 / 436 pins. No operation-derived delta is added.
Why and where an index is needed
LogicalObjectStore::put_many and put_many_hashed are direct public batch boundaries, including callers without StagedObjects. The lower Objects provider processes repeated keys independently, returning a precondition failure for a later immutable insertion; it does not coalesce the filesystem batch or preserve its unique-write accounting. General batches therefore still need duplicate resolution before provider dispatch.
The publication choke point now records object-kind membership in four u64 words. A first occurrence cannot duplicate any preceding ID and requires no object-table probe. On the first repeated kind, publication creates the existing exact-layout index once and seeds it from the already-owned unique result list. Subsequent operations use that index. This preserves #961's linear indexed path for large same-kind batches without building a second table for structurally distinct chunk/page batches. Every performed hash-slot probe is still charged before access. All 256 possible canonical u8 tags fit the membership representation.
Guarantee
- Class: H (repeated work/allocations), with I (accepting a regression into an oracle). The single private publication path admits an identity table only when kind membership can no longer establish uniqueness. Both original complete-operation pins remain unchanged.
- Proof: exhaustive production-bound comparison for all 255 nonempty subsets of the eight object kinds; 32-case shrinking property for arbitrary kind order, duplicate identities, distinct same-kind objects, raw/authenticated input and object-size errors. Candidate and old eager path have separate stores and full body/error comparisons. Existing exact N/2N accounting, forced-collision denial-before-mutation, exact physical allocation/custody and cancellation properties remain. The eager-table control restores the old production constructor and insert loop; it must fail the same original rescan allocation pin, add charged items, and preserve complete output, errors and syscall trace. Existing stat fallback, quota, cancellation, owner-allocation and birth-time controls remain.
- Entry points: raw
put_many(ordinary and native original-custody), authenticated ordinaryput_many_hashed, direct logical callers and the complete production native capture/rescan path. Authenticated native batches retain the existing typed unsupported boundary and its regression proof. No parser or concurrency primitive changes. - Gaps: none for the redundant-index class and these two main failures. All exact-head platform gates below passed.
Validation
Signed and pushed head: 17b60141d69e82e29298a1ca1ac66b6c48a32020. Worktree: Q:/wt/co-perf-main-counters.
| Platform | Focused result | Slowest suite | Build |
|---|---|---|---|
| Linux / bb2 | 16 passed; original 46 / 331 / 331 pins | 0.37 s | 7m27s |
| Windows native | 16 passed; original 43 / 324 / 436 pins | 0.09 s | 13m26s |
| macOS / ivar | 14 passed; all kind subsets and promotion/index properties | 0.05 s | 6m42s |
| Windows GNU / bb2 | lib/tests compile passed | — | 1m08s |
Every kind-disjoint batch has exactly 2 + 3*N logical allocation operations and zero identity probes, versus the independently initialized eager path's 3 + 3*N operations and nonzero probes. Complete native capture/rescan measures physical heap allocations as well as charged work. Larger repeated-kind batches retain the exact N/2N probe/allocation property and forced-collision controls.
Paired aggregate publication diagnostics for 255 batches (old/new): Linux 6.124268 / 6.206901 ms; macOS 4.822340 / 8.227502 ms; Windows isolated built-binary rerun 2.5566 / 2.5878 ms. Linux/macOS diagnostics ran alongside the other property tests; they establish no CPU-time speedup. Acceptance is the removed physical allocation and probes, unchanged complete outputs/errors/traces, and original exact pins. All suites stayed under 10 seconds.
macOS used /opt/homebrew/bin/sccache; all four lane-owned target/source/archive/driver paths were independently verified absent afterward (48 GiB free). No queued/running lane build remains. No scratch files are committed.
The unchanged-main production capture failures are recorded in Q:/lanes/main-gate/ws-32a9ae569.log: one extra physical allocation and normalized items 333/334 instead of 331. The focused filesystem tier caught the regression; my initial PR attempted to derive the added costs rather than remove them. This revision restores those tests and fixes publication.
Paired measurement command (all platforms): build cargo test --locked -p acyclic-fs --lib --features model-checking --no-run --message-format=json, then run the emitted binary directly with every_object_kind_subset_avoids_duplicate_index --nocapture. It prints aggregate old/new publication duration over 255 independently initialized paired batches and checks exact allocation/probe differences plus complete readback. Timings are debug-build diagnostics; exact physical/work pins are acceptance.
Focused gates: emitted binary filters native_capture::owner_allocation_tests::, native_capture::rescan_probe_tests::, logical_objects::tests::, logical_objects::index:: (each hard-bounded at 10 seconds). GNU compile: cargo check --locked -p acyclic-fs --lib --tests --features model-checking --target x86_64-pc-windows-gnu. macOS runs the logical/index suites with sccache; its own target and source/archive/driver are removed after the run.
Decisions
Restore the original pins and remove unconditional index work. Retain fully charged duplicate resolution for arbitrary direct batches; use ObjectId's existing kind discriminator rather than another allocation, an arbitrary small-batch cutoff or an unmetered linear duplicate scan. Own non-merge commits only; normal signed push.
Coordinator: existing acyclic-fs library qualification should include the four focused filters above; no new GitHub-hosted Rust job.