diff --git a/docs/superpowers/plans/2026-11-09-shared-resource-ownership.md b/docs/superpowers/plans/2026-11-09-shared-resource-ownership.md new file mode 100644 index 0000000..28b720e --- /dev/null +++ b/docs/superpowers/plans/2026-11-09-shared-resource-ownership.md @@ -0,0 +1,459 @@ +# Shared Resource Ownership Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement the approved plan task-by-task. Steps use checkbox syntax. Merge the planning PR and obtain implementation authorization first. + +**Goal:** Deliver #33's current proof index, #50's SDK fact correspondence and #48's bounded auxiliary response ownership without changing supported workload or claiming target fit. + +**Architecture:** Reuse the runtime's single additive derivation and the existing admission/completion owners. Bound response construction and retained storage in their current core owners. Apply a smaller handoff charge only when complete route, lifetime, container and applicable host-copy proofs support it; otherwise retain the charge and report the unfulfilled acceptance explicitly. + +**Tech Stack:** Existing Rust workspace, Serde, `CappedWriter`, SDK HTTP/body/completion APIs and existing allocation/process test owners. No new dependency, crate, SDK extension, configuration path or product limit. + +**Specification:** [Shared resource ownership](../specs/2026-11-09-shared-resource-ownership-design.md). AG approved its written scope on 2026-11-09. This plan remains proposed for joint review; implementation and merge are not authorized. + +--- + +## Baseline And Ownership + +Start from Auctioneer `499f0e540e95e1c86b730c1030aabef28de01d0a`, SDK +`598f0a2a490eb83dd31c06c024a0cefa8e049628`. Recheck relevant source and issue +state after the planning PR merges; do not chase another SDK revision as an +implicit part of this work. Read `AGENTS.md`, `crates/AGENTS.md` and +[development/test ownership](../../testing/development.md). + +Concurrent [PR #160](https://github.com/IABTechLab/auctioneer/pull/160) changes +GVL health configuration/admission, response headers and auxiliary body-read +expectations from the same baseline. It is not included in this plan's source +observations. If it lands before implementation, re-derive the authorized and +unauthorized health header/storage shapes, configuration work/lifetimes and +auxiliary/probe rows from that merged base before freezing bounds. If it lands +during implementation, rebase and repeat that reconciliation before the +storage/accounting cutover. Neither merge order may silently carry forward +the old health-work assumptions or substitute #160's new unit tests for the +existing integration owners below. + +All commands below are planned commands; their expected outcomes are not +executed evidence. Preserve feature selection, original captures, existing +test assertions, ignored-test owners, process isolation and source pins. +Run heavy Cargo commands sequentially through the cycle's ownership wrapper. +Keep incremental outputs; do not clean shared targets to prepare a run. +For every focused command, record its executable target and nonzero dispatch. +The CLI's `bidders` modules belong to `--bin auctioneer-cli`, not `--lib`; +a successful command that executes zero selected cases is not verification. +The platform module has nine existing tests at this baseline. Reconcile any +changed count to named additions/removals rather than accepting a zero match. + +| Responsibility | Files To Create Or Modify | Existing Verification Owners | +| --- | --- | --- | +| Current proof/SDK index | Create `docs/testing/resource-proof-ownership.md`; add a current-index pointer in `docs/bidders/schema-qualification.md` and `docs/testing/resource-qualification.md` | Existing schema/source reports and resource capture; no new ledger framework | +| Sync final backing | `crates/auctioneer-core/src/handlers/sync_http.rs`, `src/serialize.rs` only for shared serializer factoring | Inline units; `tests/serialize_allocation_bound.rs`, `tests/usersync_handler_alloc.rs` | +| Header/cookie ownership | `crates/auctioneer-core/src/handlers/sync_http.rs`, `src/usersync/cookie.rs` where its formatter/conversion needs a bounded change | Inline units; existing sync allocation/HTTP/integrity targets | +| Completion lifetime | `crates/auctioneer-core/src/ingress.rs`, `src/host_primitives/ingress_lease.rs` only if a demonstrated gap requires a fix | `tests/egress_binding.rs`, `tests/aux_route_write_deadline.rs`, `tests/aux_routes_wrapped.rs`, `tests/probe_output_before_boot.rs` | +| Qualified accounting correction, conditional | `crates/auctioneer-runtime/src/{aux_work,budget}.rs`, core `src/host_primitives/{gates,resource_profile}.rs` and adapter-owned envelope/profile callers only as necessary | Inline runtime/gate units and current adapter envelope tests | +| Report/parity consumers, conditional | `crates/auctioneer-cli/src/bidders/resources/{mod,catalog_comparison,records/mod,records/capture,records/catalog_continuity}.rs` | Inline units; `tests/bidders_resources.rs`, `tests/resource_boot_parity/main.rs`; original pinned catalogue baselines | + +Unqualified source paths in a row remain inside its named crate. Keep pure +unit tests inline and preserve the separate allocation/global-boot binaries. +Do not split unrelated large modules or add a test target for this package. + +## Task 1: Freeze The Actual Closure And Completed Components + +- [ ] Record a fresh clean baseline commit, SDK/lock/toolchain/features, actual + test selections and any failure before editing production code. Use the + current full acceptance matrix, not an old test-count assertion. +- [ ] Obtain executable definitions/aliases from build discovery and generated + metadata/bindings. Join current schema selection/probe reports and format + owners. Distinguish 17/18 native versus 3/4 guest serving bindings from the + larger metadata/inspection populations at the baseline; derive membership + from source rather than adding another name list. +- [ ] Create the proof index with separate C, I, V, config, sealed DTO, ordinary + decoder-audit, auxiliary, SDK-bookkeeping, ingress, codec and host-artifact + sections. For each row name source-bound proof state, source, units, + acquire/release, simultaneous storage owners, evidence limits and the owning + GitHub issue. Do not add assignee, responsible-role, next-action or live + task-status columns to this source/evidence index. +- [ ] Credit existing component models, native schema reservations, owned + receipts and #159's settings-first API. Assign unfinished discovery-derived + schema selection and C/I/V certification to #46/#47, extraction proof to + #147, DTO qualification to #150, audit to #148 and actual host handoff to + Trusted Server #1510/#76. Do not reopen implemented APIs or add unshipped + backends to the closure. +- [ ] Reconcile each remaining gap's existing GitHub issue body and native + dependency links. Record its responsible role and concrete next action on + that issue, preserving project fields and avoiding duplicate tickets. This + satisfies #33's ownership/action requirement without a second local backlog. +- [ ] Reconcile every index row to an actual report/source or explicit missing + evidence. Keep historical checkpoint prose and artifacts unchanged except + for a clearly separated current-index pointer. Commit the self-contained + index checkpoint; it delivers no resource qualification or issue closure yet. + +```sh +cargo test --locked -p auctioneer-bidder --test registry_join --test registry_source_conformance +cargo test --locked -p auctioneer-bidder --test schema_source_conformance schema_selection -- --test-threads=1 +cargo test --locked -p auctioneer-bidder --test schema_source_conformance --features insecure-bidders schema_selection -- --test-threads=1 +cargo run --locked -p auctioneer-cli -- bidders resources --check --all-adapters --capture-audit > "$RUNNER_TEMP/resource-audit-before.toml" +cargo run --locked -p auctioneer-cli -- bidders resources --check --all-adapters --audit "$RUNNER_TEMP/resource-audit-before.toml" > "$RUNNER_TEMP/resource-audit-before.md" +``` + +Expected: actual nonzero dispatch, exact report/source correspondence and a +complete source audit. These commands do not prove guest physical fit. Source +selection still using historical projections must be identified, not silently +replaced or relabeled as full executable discovery. + +## Task 2: Complete The SDK Fact And Funding Correspondence + +**Inspect:** Four adapter envelope/profile/entrypoint owners, +`crates/auctioneer-core/src/host_primitives/{gates,resource_profile}.rs`, +`src/outbound/`, CLI `src/bidders/resources_platform.rs`, and the pinned SDK +`crates/edgezero-{core,adapter-axum,adapter-cloudflare,adapter-fastly,adapter-spin}`. + +- [ ] For each adapter, join published facts and actual enforcement with the + configured ceiling, scope, populations, dispatch and artifact requirement. + Record Axum's existing connection gate, Fastly's published separate stack + and Spin's actual hosted dispatch; do not invent missing implementations. +- [ ] Trace prepared contexts, scopes, tasks, queued/completed results, boxed + receipts and teardown scratch into an existing funded term/population, or + mark the correspondence unproven. Include Fastly's recorder/configuration + keys/backend caches and their callback/instance lifetimes. +- [ ] Separate host ingress/framing from admitted application work and + application headers from provider-added fields. Preserve Cloudflare's unknown + inbound population, native operator facts, hosted Spin provenance and codec + certificate work. Assign facts/actions to #72-#75 and codec coordination #49; + no external service is contacted or deployment mutated in this task. +- [ ] Verify configured overflow and external absence remain independent in + the existing platform evaluator. Run its existing controls; if a concrete + missing case is found, add an inline failing case first and correct only the + demonstrated mismatch. Do not add code solely to restate a passing table. +- [ ] Record the exact source/API ownership and funding correspondence, its + remaining unknowns and their issue links in the index. Keep responsible + roles, verification actions and native dependency links on those GitHub + issues alone. Mark #50's coordination outcome separately from provider + fulfillment; the final handoff must not turn a missing physical + correspondence into `known`. + +```sh +cargo test --locked -p auctioneer-cli --bin auctioneer-cli bidders::resources_platform:: +cargo test --locked -p auctioneer-cli --test bidders_resources +cargo test --locked -p auctioneer-core --lib host_primitives::resource_profile:: +``` + +Expected: unknowns stay nonpassing and local demand failure is still reported. +The index is the deliverable where current behavior already conforms; it is +not a reason to change adapter configuration or the evaluator. + +## Task 3: Bound Sync Final Backing Without Changing Its Wire Output + +- [ ] Add inline controls beside `bounded_json_line` for empty, small, + intermediate, exact and over-limit bodies, varying payload and supplied cap + independently. Include all escaped characters and the final newline. Assert + literal expected bytes and absence of a partial response on failure. +- [ ] Extend `serialize_allocation_bound`/`usersync_handler_alloc` with two + unequal sizes, retained-capacity and no-growth observations, plus a positive + growable-writer control. Run the new cases before editing; record the actual + red assertions and dispatch, not an expected missing-test failure. +- [ ] Factor the current counted-size/capped-write implementation only as + needed, keeping the existing sync output cap. Use a cap-aware, nonallocating + counting writer that stops serialization on the first over-cap write; do not + use the uncapped `serialized_json_bytes` helper alone on this path. Refuse + `max < 1` first, count at most `max - 1` raw JSON bytes, and check the count + before any output-buffer reservation. For the unescaped path reserve that + count plus newline and write without growth. This adds no cap to info/health. +- [ ] For the escaped path, checked-count HTML expansion from the validated + raw UTF-8, stop on `max - 1` overflow before reserving the escaped buffer, + and reserve its full output plus newline. Fund both backing buffers + simultaneously and write through the same capped writer. +- [ ] Preserve the existing error mapping. Over-cap remains `BodyLimit`, + serializer failure remains `Serialization`, and reservation failure is a + bounded terminal internal response, with no retry or partial body. Test a + deliberately inconsistent custom serializer in both directions: growth must + fail at the reservation and shrinkage must fail the final length equality + check. No counted-size mismatch may produce a response. Do not equate + requested reserve size with a physical proof. +- [ ] Add a serializable sequence with an instrumented visited-entry counter: + over-cap counting must stop before the remaining entries are visited, while + a fitting positive control visits all entries. Observe that raw overflow + reserves neither output buffer and escaped overflow reserves no escaped + buffer. Independently vary input length, cap and escape expansion; retain + existing refusal boundaries and avoid an elapsed-time-only work assertion. +- [ ] Run full module/allocation and HTTP reference controls, then commit the + green bounded-backing change with unchanged isolate charges. + +```sh +cargo test --locked -p auctioneer-core --lib handlers::sync_http:: +cargo test --locked -p auctioneer-core --lib serialize:: +cargo test --locked -p auctioneer-core --lib handlers::tests:: +cargo test --locked -p auctioneer-core --test serialize_allocation_bound --test usersync_handler_alloc +cargo test --locked -p auctioneer-core --test usersync_http --test usersync_wire_reference --test usersync_template --test usersync_integrity +``` + +Expected: byte-identical successful output, unchanged refusal boundaries, +nonzero positive controls and bounded construction/final backing. A change in +measured allocations is reported separately from an accounted reduction. +The inline `handlers::tests::` cases drive production responses against the +frozen wire oracle. `usersync_wire_reference` checks fixture metadata only; +it is retained but cannot substitute for that production oracle. + +## Task 4: Establish Header And Complete-Cookie Ownership + +- [ ] Enumerate the actual final header shapes in `sync_http`, info, health, + fallback/error and probe owners. Record map capacities and all duplicate-value + allocations, name/value backing and cookie/CORS formatting transitions for + the locked `http`, `bytes`, cookie and SDK versions. Do not derive retained + bytes merely from `headers.len()` or serialized field size. +- [ ] Use the six existing health integration owners below for authorized, + unauthorized, fail-closed, observer, stale-observer and documented-contract + coverage. Record nonzero dispatch per target. At this baseline + `--lib handlers::health_gvl::` selects zero tests and is not a verification + command; any later unit cases supplement, not replace, these owners. + Reconcile #160's merge-order effects as specified under Baseline And Ownership. +- [ ] Add owner-local inline cases and allocation cases varying unique fields, + duplicate values, name/value length, CORS backing size and cookie length + independently. Include an oversized backing with a small surviving slice, + insertion then removal, final failure, and a positive allocation control. + Prove before-state presence and backing sharing before asserting detachment. +- [ ] Where the source proof requires a change, build the reviewed final + application header set through its current helper. Copy only bounded final + names/values when detachment is required; a fresh map may replace retained + oversized capacity only while old/new map and value backing are both funded. + Preserve valid duplicates, `Vary`, CORS and complete cookie bytes. Do not add + a blanket header ceiling to an endpoint with a different existing contract. +- [ ] Account the existing cookie preflight, formatter, field string and + `HeaderValue` conversion peak. Replace a growable stage only with an existing + compatible bounded mechanism and literal byte/reference checks; do not add + a cookie encoder or hand-written replacement grammar for this work. +- [ ] Write the source/ABI/container bound and its actual allocator scope. + Validate native observations and applicable guest layouts independently. + If a locked library contract cannot justify a necessary bound, record its + exact missing fact/owner, retain charges and #48's unmet criterion, and stop + the affected accounting cutover. A new SDK/layout API or vendor patch goes + back to design review; do not invent a private layout or grow an allowance. +- [ ] Run all existing header, cookie, route and allocation owners. Commit only + green independently useful ownership changes; do not mark #48 complete from + a source-only table or assigning the remaining proof to #50. + +```sh +cargo test --locked -p auctioneer-core --lib usersync::cookie:: +cargo test --locked -p auctioneer-core --lib handlers::sync_http:: +cargo test --locked -p auctioneer-core --lib handlers::tests:: +cargo test --locked -p auctioneer-core --lib handlers::info_bidders:: +cargo test --locked -p auctioneer-core --test health_gvl --test health_gvl_auth --test health_gvl_failclosed +cargo test --locked -p auctioneer-core --test health_gvl_observer --test health_gvl_stale_observer --test health_gvl_docs +cargo test --locked -p auctioneer-core --test usersync_handler_alloc --test serialize_allocation_bound +cargo test --locked -p auctioneer-core --test usersync_http --test usersync_routes --test usersync_integrity +``` + +Expected: existing public behavior and application limits stay intact; a +capacity/ownership regression makes a named new case fail. Native requested +heap is not presented as guest physical memory or a whole-wire header cap. + +## Task 5: Verify Terminal Ownership Across All Contributing Routes + +- [ ] Extend the existing route/wrapper coverage join so every contributing + auxiliary/fallback response and distinct probe path is accounted for. Reuse + registered-route parsing; do not write a second manual route list that can + omit new registration silently. +- [ ] Use the existing real SDK admission/response envelope to suspend egress + after handler return. Observe the original auxiliary route gate and work + ledger remaining occupied, then separately exercise completion success and + completion drop/abandonment. Each must release exactly once. +- [ ] Cover saturation/recovery, header/body construction refusal, config/store + failure, body-read timeout, cancellation during construction, cancellation + after handoff and framework-generated fallback after the grant is dropped. + Do not add a second auxiliary acquisition or new public fault hook. +- [ ] Keep probe-config work, output permits/cache, static refusal and auction + work/egress counters independent. Exercise zero auxiliary permits and unequal + nonzero populations. If an existing lifetime control already pins a property, + extend it for the missing join rather than cloning its whole fixture. +- [ ] Fix only a reproduced ownership/deadline gap. Otherwise retain the current + completion wiring and commit the proof controls; no gratuitous lifetime split. + +```sh +cargo test --locked -p auctioneer-core --test egress_binding --test ingress_admission +cargo test --locked -p auctioneer-core --test aux_routes_wrapped --test aux_route_write_deadline --test aux_request_body_bounds +cargo test --locked -p auctioneer-core --test probe_output_before_boot --test probe_pending_reads --test probe_before_boot +``` + +Expected: actual positive held counters, exhausted admissions, original +deadlines and complete recovery. Handler return alone must not free terminal +output storage. Filtered or zero-route dispatch is not coverage. + +## Task 6: Freeze Any Exact Accounting Correction Before Its Cutover + +This task is conditional on Tasks 3-5 supplying the complete required bounds. +If they do not, preserve production accounting and report the blocked part of +#48. Do not add a write-only descriptor, zero placeholder or unused helper. + +- [ ] Record `A` (auxiliary population), `P = max(A, probe minimum)`, `D` + (unchanged declared per-slot work peak), `H` (applicable handoff multiplicity), + `R` (proved application response-retained storage) and `T` (transmit state). + Show the exact owners behind each symbol and whether `D` already includes + one `R`. Include all output/header/cookie/refusal routes and actual target + copy contracts, not just a cookie-sync maximum. +- [ ] The current charged expression is `(A + P) * (H * D + T)`. A possible + correction, **only if the proofs establish its premises**, is + `A * (D + (H - 1) * R + T) + P * D`: retain all declared work slack, replace + only additional response-copy work, and charge probe output/cache separately + as already done. Prove that probe configuration creates no additional + uncharged transmit/copy domain. If any premise fails, retain that existing + component; do not apply the expression as a default saving. +- [ ] Freeze the actual old/new checked derivation, storage source/ABI witness, + affected adapter rows, exact delta and residual gap in the current index + before changing `budget.rs`. `R <= D`, `H >= 1` and every required source + bound must be established. A different ownership/equation proposal outside + the approved additive design returns to review. +- [ ] Add failing inline runtime/gate cases varying `A`, probe minimum/output + population, `D`, `R`, `H` and `T` independently, including zero, one, unequal + nonzero, overflow and one-byte boundaries. Expected deltas use independent + values; removing the changed summand or changing the wrong population must + fail a specific case. Preserve every existing unrelated term assertion. +- [ ] Implement the minimal derivation in runtime and pass owning bound data + through the existing profile/bootstrap/CLI consumers. Update exhaustive + destructuring, capture component receipts and actual boot/CLI projections in + the same checkpoint; the CLI must not calculate its own substitute sum. +- [ ] Before changing current receipt arithmetic or shape, add cutover controls + in the existing records owner for both `catalog-baseline.toml` and + `catalog-baseline-patched-lock.toml`. Their original bytes/pins, component + arithmetic and existing continuity checks must remain valid. Today + `validate_snapshot` calls `validate_components` before its `Mode` branch; + changing `component_values` alone would apply new arithmetic to frozen + records. Separate explicit supported historical validation from current + validation before that shared call. Do not skip arithmetic for all frozen + inputs or default missing current bound fields to zero. Test altered old + receipts and incomplete current receipts are still refused; extending DTOs + must not make either archived format unreadable. +- [ ] Trace `catalog_comparison::{check_capture,compare,growth_between}` and + `catalog_continuity` through the same cutover. They currently rederive + captures and allow only matched-input, nonnegative catalogue growth. Keep + supported historical validation/continuity and same-model comparisons + working; refuse unsupported mixed-accounting-model comparisons explicitly. + Do not reinterpret a handoff saving as catalogue growth, broaden the growth + allowance, drop a capture check or rewrite a pinned baseline. New accounting + evidence remains a separate current capture, not a recertified old record. +- [ ] Run the real closed-graph parity target and existing current-resource + report/refusal controls. Refresh new evidence, never old pinned records. + Report observed allocation differences and accounted savings separately; + missing SDK facts and target overflow must remain nonpassing. + +```sh +cargo test --locked -p auctioneer-runtime --lib +cargo test --locked -p auctioneer-core --lib host_primitives::gates:: +cargo test --locked -p auctioneer-cli --bin auctioneer-cli bidders::resources +cargo test --locked -p auctioneer-cli --bin auctioneer-cli bidders::resources::records::tests::pre_catalog_snapshot_pins_source_terms_and_unqualified_targets -- --exact +cargo test --locked -p auctioneer-cli --bin auctioneer-cli bidders::resources::records::tests::catalog_comparison_loads_only_the_frozen_reviewed_baseline_bytes -- --exact +cargo test --locked -p auctioneer-cli --bin auctioneer-cli bidders::resources::records::tests::patched_lock_catalog_baseline_retains_origin_and_chooser_growth -- --exact +cargo test --locked -p auctioneer-cli --test bidders_resources +cargo test --locked -p auctioneer-cli --test resource_boot_parity +cargo run --locked -p auctioneer-cli -- bidders resources --check --all-adapters --capture-audit > "$RUNNER_TEMP/resource-audit-after.toml" +cargo run --locked -p auctioneer-cli -- bidders resources --check --all-adapters --audit "$RUNNER_TEMP/resource-audit-after.toml" > "$RUNNER_TEMP/resource-audit-after.md" +bash .github/workflows/scripts/test/reconcile-resource-fit.sh +``` + +Expected: exact production/CLI parity and truthful residual refusal. The last +script permits the CLI's documented exit 1 only when its JSON fit verdicts +agree; it is not a target-fit pass. Current/future new evidence binds its own +source/SDK/artifact, not this planning baseline by substitution. +Each exact historical regression above must execute one case, not zero. Run +the new cutover/refusal cases too; the existing frozen-coefficient test alone +does not cover changing auxiliary component arithmetic. + +## Task 7: Complete Verification And The Review Handoff + +- [ ] Execute Appendix A and the applicable complete workflows at the final + implementation head. Run every new named case with nonzero dispatch. Keep + all unrelated test assertions, full-source checks and feature graphs intact. +- [ ] Demonstrate guard sensitivity with isolated temporary mutations for each + consequential new storage/lifetime/accounting check. Retain exact failing + output and restore each mutation before the complete green run; do not mutate + another reviewer/user checkout or leave a production test hook. +- [ ] Record commands, exit codes, actual result lines, commit, artifact/ABI, + features/SDK/lock, observations versus bounds, accounted delta, residual + demand and blocked checks. Do not report guest execution from `--no-run`. +- [ ] Update the current index, keeping #33, #50 and #48 outcomes distinct. If + #48 is partial, state the exact unmet criterion; do not claim the package or + whole resource epic complete merely because the index and body fix landed. +- [ ] Request independent review on the separate implementation PR. Use + closing keywords only for actually delivered issue acceptance; leave provider, + schema, target certification and host handoff issues open. Never resolve + reviewer threads as Creator or merge without the user's decision. + +## Appendix A: Acceptance Commands And Prerequisites + +Use `.tool-versions` and the source/SDK locator in +[development/test ownership](../../testing/development.md). Supply one pinned +bidder source repository with both required immutable revisions, +`PREBID_SERVER_DIR`, `EDGEZERO_SDK_ROOT` and a disposable `RUNNER_TEMP`. No +supplemental checkout variable or untracked private provider config is needed. +Missing source, tool, provider or execution prerequisites are blockers, not +permission to ignore, relabel or weaken a case. Current CI is authoritative +for tool installation and the complete matrix; do not copy workflow bodies +into a Rust snapshot test. + +### Native Graphs And Focused Conformance + +```sh +cargo test --locked -p auctioneer-config-data --lib --test codegen --test disclosure_codegen --test gvl_archive --test smoke --test url_safety --test activity --test product_defaults_snapshot --test simple_fold --test usersync_config --test usersync_callback_validation --test bidder_bindings --test bidder_storage +cargo test --locked -p auctioneer-config --lib --test root_shape --features build-support +cargo test --locked -p auctioneer-bidder -p auctioneer-exchange -p auctioneer-core --all-targets +cargo test --locked --workspace --all-targets --no-fail-fast -- --skip tests::selected_schema_source_producer_records_exact_real_dispatch_without_qualification --skip tests::controlled_native_acceptance_requires_all_producers_but_never_completes_the_full_catalog +cargo test --locked --workspace --all-targets --features insecure-bidders --exclude auctioneer-config-data --no-fail-fast -- --skip tests::selected_schema_source_producer_records_exact_real_dispatch_without_qualification --skip tests::controlled_native_acceptance_requires_all_producers_but_never_completes_the_full_catalog +cargo test --locked -p auctioneer-config-data --features "insecure-bidders test-harness-override" --lib --test codegen --test gvl_archive --test smoke --test url_safety --test bidder_bindings --test bidder_storage +cargo test --locked -p auctioneer-cli --all-targets -- --skip tests::selected_schema_source_producer_records_exact_real_dispatch_without_qualification --skip tests::controlled_native_acceptance_requires_all_producers_but_never_completes_the_full_catalog +cargo test --locked -p auctioneer-cli --no-default-features --all-targets +cargo test --locked -p auctioneer-core -p auctioneer-adapter-cloudflare -p auctioneer-adapter-fastly -p auctioneer-adapter-spin --all-targets --features conformance-suite +cargo test --locked -p auctioneer-adapter-axum --test cancellation --test wave_transport --test privacy_wire --features insecure-bidders +``` + +The two libtest skips above retain their existing mandatory once-owned +producer command below. `--all-targets` alone skips `test = false` and +unsatisfied `required-features`; never describe it as the complete matrix. + +### Mandatory Closed-Graph Owners And Gates + +```sh +bash .github/workflows/scripts/test/verify-schema-compiler-conformance.sh schema +bash .github/workflows/scripts/test/verify-schema-compiler-conformance.sh settings +bash .github/workflows/scripts/test/verify-schema-compiler-conformance.sh exports +bash .github/workflows/scripts/test/verify-schema-compiler-conformance.sh discovery +bash .github/workflows/scripts/test/verify-config-build-failures.sh +bash .github/workflows/scripts/test/verify-evidence-producers.sh +bash .github/workflows/scripts/test/verify-schema-core-secure.sh +bash .github/workflows/scripts/test/verify-schema-core-development.sh +bash .github/workflows/scripts/test/verify-schema-metadata-secure.sh +bash .github/workflows/scripts/test/verify-schema-metadata-development.sh +bash .github/workflows/scripts/test/execute-catalog-publication.sh +cargo test --locked -p auctioneer-cli --test resource_boot_parity +cargo test --locked -p auctioneer-bidder --test test_isolation +./scripts/check-batch-rebase.sh +./scripts/check-roster.sh +./scripts/check-roster-fixtures.sh +./scripts/check-secure-usersync.sh +./scripts/check-raw-json-fixtures.sh +cargo fmt --all -- --check +cargo clippy --locked --workspace --all-targets -- -D warnings +cargo clippy --locked --workspace --all-targets --features insecure-bidders -- -D warnings +cargo clippy --locked -p auctioneer-core -p auctioneer-adapter-cloudflare -p auctioneer-adapter-fastly -p auctioneer-adapter-spin --all-targets --features conformance-suite -- -D warnings +``` + +Also run the unchanged feature-binding/source-selection, bidder provenance, +browser-shutdown, guest check/lint/test-link/runtime and provider packaging +steps from `.github/workflows/test.yml`, `format.yml`, `ci-cloudflare.yml`, +`ci-fastly.yml` and `ci-spin.yml`. Those scripts/workflows remain the single +complete executable authority; Appendix A does not exclude omitted rows or +replace actual hosted job results. No credentialed deployment test is implied +by a local vendor build or dry run. + +### Actual Guest Evidence + +Application storage proofs require their applicable guest ABI/execution +evidence, not only packaging. Reuse current guest runners and bound artifacts +where supported. If a provider-only allocation domain cannot be measured or +proved locally, preserve the unknown, exact owner and next verification; it +blocks that claimed bound or target readiness. Do not create a new harness, +skip a target, or claim #48's outstanding criterion completed to finish a PR. + +## Planning Publication + +This phase changes only the specification and plan. Publish them together +after document review and authorized publication; include `PRfection phase: +plan`, `Refs #33`, `Refs #50`, `Refs #48`, actual executed checks and remaining +validation blockers. Keep all three issues open and preserve project fields. +Planning approval is neither implementation approval nor merge authorization. diff --git a/docs/superpowers/specs/2026-11-09-shared-resource-ownership-design.md b/docs/superpowers/specs/2026-11-09-shared-resource-ownership-design.md new file mode 100644 index 0000000..2f930db --- /dev/null +++ b/docs/superpowers/specs/2026-11-09-shared-resource-ownership-design.md @@ -0,0 +1,413 @@ +# Shared Resource Ownership And Auxiliary Response Accounting + +Date: 2026-11-09 +Status: Draft for document review; not implementation authorization. +Issues: [#33](https://github.com/IABTechLab/auctioneer/issues/33), +[#50](https://github.com/IABTechLab/auctioneer/issues/50), +[#48](https://github.com/IABTechLab/auctioneer/issues/48). +Parent: [resource qualification](https://github.com/IABTechLab/auctioneer/issues/9). +Code baseline: `499f0e540e95e1c86b730c1030aabef28de01d0a`. +SDK baseline: `598f0a2a490eb83dd31c06c024a0cefa8e049628`. + +## 1. Outcome And Scope + +Deliver one bounded package through the existing ownership and accounting +paths. Reconcile what already works, assign the remaining proof and SDK +obligations, and close application-owned auxiliary response storage and +lifetime gaps. Independently justified accounting corrections are permitted; +projected savings, target fit and complete schema certification are not +promised outcomes. + +AG approved retaining the current permits, completion callbacks and single +additive budget, bounding storage and proving any corrections. This decision +does not approve implementation before the written specification and plan +are reviewed and merged. + +The package has three ordered stages with separate acceptance: + +1. **#33:** reconcile the actual shipped closure and existing components. +2. **#50:** identify the exact adapter, external owner and next action for + each remaining SDK or provider fact, recording ownership and actions on + the existing GitHub issues rather than in a local status ledger. +3. **#48:** implement and verify auxiliary output storage and lifetime + bounds; change accounting only where the complete required proof exists. + +Keep the existing dependency direction: #33 precedes #50 and #48. A joint +delivery does not make #33 depend on finishing those children, and a merged +planning PR closes none of the three issues. + +### Non-Goals + +- No new configuration representation, loader, startup settings, cache or + operator knob. Preserve the single runtime policy and existing SDK flow. +- No new crate, resource framework, regex engine, bidder cohort or manually + maintained executable bidder list. +- No changed workload limits, concurrency, platform ceilings, headroom floors, + schema semantics, decoder caps, privacy or user-sync behavior. +- No broad schema certification from #46/#47, configuration extraction proof + from #147, sealed DTO qualification from #150 or decoder audit from #148. + Reconcile these obligations without doing their independent projects here. +- No claim that this package enables Fastly guest serving or proves the + combined Trusted Server artifact. Those remain #73, #88 and #76 outcomes. +- No rewriting historical evidence, pinned captures or their measured bytes. + +## 2. Current Implementation And Actual Gaps + +The following are already implemented at the baseline and must be reused: + +| Component | Existing owner | Boundary to preserve | +| --- | --- | --- | +| Shared checked arithmetic and all isolate terms | `auctioneer-runtime/src/budget.rs` | One additive total consumed by bootstrap and the CLI | +| Auxiliary materialization caps and reservation | `auctioneer-runtime/src/aux_work.rs` | Admission before configuration/input work; pinned GVL remains separate | +| Auxiliary route and work lifetime | `auctioneer-core/src/ingress.rs::admit_aux` | The route completion owns both the permit and work receipt through terminal egress | +| Egress ownership and deadlines | `host_primitives/ingress_lease.rs`, `handlers/aux_egress.rs` | No early release at grant drop, handler return or wrapper return | +| Exact-size info/health serialization | `auctioneer-core/src/serialize.rs::serialize_json_exact` | Count, reserve fallibly and serialize without buffer growth | +| Detached CORS storage | `handlers/sync_http.rs::SyncBrowserPolicy::allowed_origin` | Copy the matched configured origin, not an ingress-backed slice | +| Typed host bridge | `auctioneer-core` public bridge from #159 | Settings first; original funding and services; actual host acceptance remains separate | +| Current resource reports and strict/boot parity | Existing CLI `bidders resources`, runtime derivation and `resource_boot_parity` | Actual independently executed bootstrap and strict CLI, not two calls to one pure helper | + +Paths above name crate-local source owners; unqualified paths are inside +`crates/auctioneer-core/src/`. These are source observations, not new +allocation measurements or guest proofs. + +At this baseline, native secure/development builds have 17/18 executable +serving bindings and use 15/16 effective parameter schemas; guests have only +3/4 serving bindings because `native_only` remains enforced. Metadata +declarations and inspection membership are not thereby reduced to the guest +serving subset. The selected regex closure contains +four generated helpers and one shared hostname program, with hostname and +UUID format callbacks. Equal patterns in different generated owners are not +therefore shared storage. The controlled-native reservation and existing +schema component/constructor/cache models are implemented, but complete +worst-case certification is not. Record these as baseline observations, not +new hand-maintained membership gates. + +Build-time bidder discovery already exists. Some schema staging and source +selection controls still use pinned priority/supplemental projections; +discovery-derived qualification selection is remaining #46/#47 work, not a +reason to rebuild the registry in this package. The current implementation +and the controlled-native amendment take precedence over older checkpoint +prose saying no serving validation exists. + +The sync `BoundedBody` checks length before append but uses a growable `Vec`. +The HTML-escaping path can retain raw and escaped buffers simultaneously; +the final newline is another write. Application header checks occur after +construction and measure serialized count/bytes, not retained map capacity, +duplicate-value storage or an earlier larger allocation. Cookie formatting +and conversion into `HeaderValue` have their own overlapping allocations. +These distinctions must be resolved before granting handoff credit. + +The current auxiliary isolate term multiplies the entire declared auxiliary +work peak by the target handoff factor for both auxiliary and independent +probe-configuration populations. It is conservative, not by itself a bug. +Multiplying less requires proof that the omitted work is not copied or live +in the handoff domain. A smaller measured receipt does not reduce an unchanged +declared isolate slot. + +## 3. One Current Proof And Ownership Index + +Add `docs/testing/resource-proof-ownership.md` as a current, source-linked +index, not another issue tracker or parallel accounting implementation. Use +the existing resource reports, schema selection/probe reports and GitHub +issues as its inputs. Do not introduce a custom discovery command or proof +serialization framework for this package. + +Each applicable row records: + +- Scope: application/feature lane, adapter and ABI, source and dependency + identities, and the owning executable schema/helper/format/alias closure. +- Proof state: implemented component, demonstrated property, remaining proof, + external fact or not applicable with a concrete reason. An observation is + explicitly an observation, never an implicit bound. +- Resource: units, allocation owner, construction/admission point, lifetime + end, simultaneous population and overlap with adjacent owners. +- Evidence: exact source/API references, existing test or artifact producer, + missing experiment/proof and the existing GitHub issue that owns the gap. + +Allocation owners above are code/storage owners, not assignees. Responsible +roles, concrete next actions, task status, priorities and blockers belong +only on the linked GitHub issues, with native dependency links. Reconcile +those issue records when producing the index; do not copy their mutable +tracking fields into index rows or create another local action ledger. + +Separate retained schema program/storage **C**, initialization peak/work **I** +and per-validation cache/scratch/work/stack **V**. Also name configuration, +sealed response decoding, ordinary decoder audit, auxiliary handoff, host +bookkeeping, ingress/framing, codec and combined-host obligations. Reuse +implemented compiler/library layouts, caches, owned receipts and controls; +do not reset their status because complete target certification is pending. + +Inventory executable definitions and public aliases from current generated +metadata/factory discovery, including runtime-disabled definitions present +in the build. Distinguish imported non-executable catalogue entries and +schema artifacts used only by full-catalogue controls. Preserve those +controls, but do not turn unused schemas or private engine alternatives into +new serving prerequisites. Record any existing source-selection limitation +honestly rather than relabeling a cohort report as complete discovery. + +This index is authoritative only for its stated baseline. Updates bind the +new source and SDK identities and revisit changed owners; historical reports +are cited as history, not silently recertified. GitHub retains task status, +priority, blockers and final acceptance. + +## 4. SDK And Adapter Fact Correspondence + +Inspect the pinned SDK implementation and the actual adapter entrypoints, +rather than copying an earlier missing-fact list. For Axum, Cloudflare, +Fastly and Akamai Functions hosted Spin, record separately: + +| Obligation | Required correspondence | +| --- | --- | +| Ceiling and scope | Published or operator-defined ceiling, what memory it covers, configured ceiling and applicable headroom | +| Population | Inbound/parser and body/outbound populations, actual enforcement, scope and independent application admission classes | +| Host ingress/framing | Parser, request-head/body backing and provider memory ownership before and after admission, including refusal and cancellation | +| Host bookkeeping | Prepared contexts, scopes, task registrations, queues, ledgers, boxed receipts, cleanup scratch and Fastly registration recorder/configuration-set storage | +| Codecs and output | Reachable decoder state/copies, response body/header conversion, host-added framing and the exact handoff contract | +| Execution identity | Actual adapter dispatch and artifact/ABI; a hosted profile name alone cannot establish deployment provenance | +| Separate stack | Published allowance versus application-required stack; the latter belongs to Auctioneer target qualification | + +The baseline already supplies these facts; do not file them as missing APIs: + +| Adapter | Provided or enforced today | Still distinct from proof of target fit | +| --- | --- | --- | +| Axum | A finite connection gate before parser construction; operator-configured SDK ceiling/population/ingress facts remain unknown | Actual operator quota/scope, runner settings and complete host/allocator correspondence; the gate is not absent | +| Fastly | 128,000,000-byte primary allowance, separate 1,000,000-byte stack and one execution-scoped inbound request | Application stack bound and host-ingress/allocator correspondence | +| Cloudflare | 128,000,000-byte instance-scoped allowance | Inbound population and host-ingress accounting; the declared outbound limit is not an inbound guarantee | +| Hosted Spin | 134,217,728-byte execution-scoped hosted default, inbound population one, and hosted metadata passed to actual dispatch | Authorized deployment provenance and host-ingress/allocator correspondence; no current generic/hosted dispatch mismatch is established | + +Source facts are in the four pinned SDK adapter `src/lib.rs` declarations, +Axum's `dev_server.rs`/`ingress_config.rs`, and Auctioneer's adapter envelope +and dispatch owners. Current SDK codec reservations and scope/task/receipt +implementations also exist; their full allocator and simultaneous-owner +correspondence is the obligation, not implementing another decoder or task +system. #49 owns codec-certificate coordination; applicable certification +remains adapter-specific. + +A published SDK subtotal is a usable component, not automatically a complete +mapping to Auctioneer's allocator, populations or charged memory scope. +A missing physical correspondence does not erase an implemented SDK feature. +Record source/API-contract facts separately from target-executed observations. + +A configured-ceiling overflow is an Auctioneer demand failure even when the +platform ceiling is unknown. External absence and arithmetic overflow can +coexist; retain both. Never use an unknown provider population as zero, reuse +an outbound limit as an inbound limit, or credit a native substitute as a +guest layout. An unresolved fact remains nonpassing in the existing fit +verdict. + +Application response-header limits exclude fields added later by a host or +provider. Prove the application storage domain here; assign the provider +domain explicitly. Fastly's application stack belongs to #73, not an SDK +implementer merely because the SDK publishes its separate-stack allowance. + +#50 can close when this fact/owner/action mapping is complete. Provider fact +fulfillment and physical target certification stay in #72-#75; assigning an +owner cannot make an unknown value known or change `required_fit` to true. + +## 5. Auxiliary Output Storage Contract + +### 5.1 Route Coverage + +Derive coverage from the existing registered routes and wrapper/egress +controls. Do not create a handwritten bidder list or silently limit the +proof to user-sync success. Cover: + +- Cookie-sync, getuids and setuid success, preflight and each failure shape. +- Bidder list, single detail and all-details output, including runtime aliases, + disabling, unknown names and invalid query values. +- Authorized and unauthorized GVL health output. +- Unknown route, wrong method, bounded-drain refusal and rendered config, + input, allocation and handler failures. +- Probe configuration and its distinct status output/cache; preboot and + stale-App funding refusals; independent static and auction responses as + adjacent, disjoint owners. + +Keep output bytes, status, header multiplicity/order where significant, +content type, newline/HTML escaping, cookie signing, CORS policy, callback +encoding, error isolation and write-deadline anchoring unchanged. No generic +new response ceiling may narrow an existing info or health endpoint. + +### 5.2 Body And Cookie Construction + +Use the existing capped-writer/size-counter ownership rather than a second +serializer. For a cap-bearing sync response, stop counting at the existing +payload cap before reserving output; an uncapped size counter must not replace +early over-cap refusal. Check raw and escaped sizes, including the final +newline, against that cap before their respective fallible reservations. +Prevent buffer growth while writing. For HTML escaping, account raw and +escaped backing and scratch simultaneously or eliminate one only with +equivalent bytes and an independent lifetime proof. Require the second pass +to emit exactly the counted size: both growth and shrinkage are terminal +count/serialize disagreement. Allocation failure is also terminal: no retry, +partial JSON or unbounded fallback. This does not add a cap to info/health +serialization, whose existing contract is counted-size reservation only. + +A logical cap and `try_reserve_exact` alone are not a universal physical +allocation proof. Record actual retained capacity and the applicable +container/allocator contract, source-bound ABI layout and requested versus +physical allocation distinction. Do not claim provider memory or RSS from a +native counting allocator. + +Include the complete cookie formatter, its input, formatted field, header +conversion and retained header backing. Preserve the existing 4-KiB complete +cookie ceiling and preflight/refusal behavior. Temporary formatter storage +belongs to construction work; response-retained backing belongs to handoff. +Never count one shared backing twice or assume borrowed bytes are detached. + +### 5.3 Application Header Storage + +Bound both materialization and retained storage: map buckets/indices, +duplicate-value storage, header name/value backing, capacity retained after +removal, CORS copying, `Vary`, the complete cookie field and formatter overlap. +Use the current `http`/SDK APIs and source contracts; do not duplicate private +Rust layouts in Auctioneer or infer capacity from serialized length alone. + +Prefer constructing only the final reviewed application header set through +the owning helper. Preserve valid duplicate values. Rejecting an oversized +input after copying it is not a preallocation bound. Prove the error/fallback +header set as well, including when normal response construction has failed. + +Where the locked library or provider contract cannot support a physical +bound, retain the conservative charge and record the exact missing fact and +owner. Do not fabricate a per-entry constant or widen a reservation from an +observed peak until tests pass. A new dependency, vendor patch, product limit +or SDK extension requires separate review, not an implicit plan fallback. + +## 6. Funding, Populations And Accounting Changes + +Retain `admit_aux`'s single acquisition and terminal completion. No handler +acquires a second auxiliary permit. No work receipt is released at handler +return while funded response backing remains live. Receipt/permit destruction +must still be exactly once on send success, failure, timeout, cancellation, +abandonment, fallback and partially constructed responses. + +Preserve these distinct populations: + +- Auxiliary requests `A`, holding their permit and work receipt to terminal. +- Independent probe-configuration work `P = max(A, PROBE_CONFIG_MIN_PERMITS)`, + ending after configuration/render/publication, not after probe transmission. +- Independently gated probe outputs and the cached probe response. +- Auction work, auction egress and static terminal responses. +- Separately pinned GVL versions, including versions held past cache eviction. + +Do not replace their additive accounting with a startup/steady maximum or +claim they cannot overlap merely because one request uses only one route. +For `A = 0`, probe configuration/output/cache still require their funding. + +An accounting correction requires an explicit old/new derivation before its +production edit. For every affected term, record units, concrete storage +owners, enforced populations, overlapping capacities, target scope, exact +charged delta and residual demand. Keep declared work slack funded; do not +subtract output twice because it already appears in `AuxWorkCharges.output`. + +In particular, replacing `handoff_factor * declared_work` with a smaller +expression requires a source-bound upper bound on the retained response +domain for **every contributing route and error path**, plus the applicable +host-copy contract. Removing probe-config transmit/copy charges requires a +separate proof that independently charged probe output covers that domain +without dropping probe-config construction/storage overlap. A route-only +bound cannot justify changing a combined auxiliary/probe term. + +Reuse the runtime checked derivation, existing adapter-owned profiles and +existing core/CLI report consumers. No CLI-local replacement equation or +second numeric descriptor assembled from copied private layouts is allowed. +If the complete proof is unavailable, ship the bounded ownership improvement +with unchanged charges; #48 stays open for any unfulfilled acceptance. + +Keep current accounting distinct from validation of original pinned resource +records. A cutover must preserve both catalogue baselines and their historical +arithmetic checks without rewriting their bytes or applying new coefficients +to old receipts. Current records must still require all current bound data. +Retain the catalogue comparison's matched-input, growth-only contract; +accounting savings are not catalogue growth. Reject unsupported mixed-model +comparisons rather than relaxing that gate or recertifying old evidence. + +## 7. Independent Verification + +The implementation plan must name existing owner targets and full commands. +Expected results in a plan are not executed evidence. Preserve the full CI +matrix, hermetic compiler/producer owners, feature lanes and guest checks. +Do not duplicate expensive suites in new binaries merely for this package. + +Required new or extended controls: + +1. **Body backing:** empty, small, intermediate, at-limit and limit-plus-one + inputs; independently varied input size/output cap; HTML expansion and + newline boundaries; no growth, partial output or hidden retry. Exact wire + expectations must not be recomputed by the writer under test. Over-cap + counting must stop before output allocation and before walking the remaining + serializable entries; cover raw and escaped overflow separately. Exercise + second-pass growth and shrinkage, and execute the production handler wire + oracle rather than only fixture metadata checks. +2. **Headers/cookie:** vary count, name bytes, value bytes, duplicate values, + CORS input backing and cookie bytes independently. Cover insertion/removal, + construction peak, final retained storage and refusal. Include positive + allocator controls and at least two unequal sizes with a differential + bound, not a generous aggregate slack multiplier. +3. **Lifetime:** suspend real egress after handler return and observe original + route/work counters; exhaust and recover; cancel during construction and + after handoff; exercise errors and abandonment; prove no double acquisition + or release. Test completion execution and completion drop separately. +4. **Population/accounting:** vary auxiliary, probe-config minimum, probe + output, static and auction counts independently. Test zero, one and unequal + nonzero populations, checked overflow and one-byte boundaries. Expected + deltas come from independent literals/source layouts, not the constant or + helper being asserted. +5. **Boot/strict parity:** execute the real bootstrap and real CLI subprocesses + in the existing isolated harness. Preserve independently fitting and + overflowing rows, configured/platform ceiling order and missing facts. +6. **Adapter correspondence:** execute applicable native/guest controls and + record artifact, ABI, features, source and SDK identities. Compilation is + not execution; missing provider facts remain explicit. No native result + substitutes for guest/host physical proof. +7. **Negative sensitivity:** show each new consequential guard rejects its + independent failing case. Source inventory, filtered dispatch and route + coverage checks must reject omitted rows, not pass with zero iterations. + +Unit cases stay inline. Use `serialize_allocation_bound`, +`usersync_handler_alloc`, the existing auxiliary/egress integration owners, +runtime units, `bidders_resources` and `resource_boot_parity` where applicable. +Process-global gates, cold state and allocation counters retain isolation. +Fixtures use capable `fake_` bidders outside real bidder-owned protocols. + +## 8. Acceptance And Handoff + +**#33 complete:** a current source-bound index accounts for the actual shipped +closure and completed components, separating every remaining proof/fact and +citing its owning issue. Each linked GitHub issue records the responsible +role and concrete next action, with native dependency links for its blockers. +No duplicate resource framework, local task ledger or reopened delivered +capability. + +**#50 complete:** the four adapter fact correspondences are reconciled against +the pinned SDK and current dispatch; unknowns, local demand failures, separate +stack and provider-added output domains remain distinct. Every missing fact +has its external/internal owner and verification action recorded on its +linked GitHub issue, not duplicated in the source-bound index. Completion +records coordination, not fulfillment or qualification. + +**#48 complete:** all applicable application-owned auxiliary output backing, +headers, cookie construction and terminal overlap have justified bounds and +passing independent controls. Any changed charge has its complete source, +lifetime, ABI/scope and population proof with real boot/strict parity. Retain +the issue open if a required bound or control remains blocked; assigning that +gap to another owner does not itself satisfy the criterion. + +The implementation handoff states each issue's actual outcome separately, +accounted versus observed changes, residual demand, unavailable checks and +remaining external prerequisites. Keep #46/#47, adapter certification and the +actual Trusted Server host handoff open until their own acceptance is met. +No unchecked gap is converted to `qualified`, and no merge closes an epic +merely because this package has landed. + +## 9. Review Sequence + +1. Review this written specification, including its independent issue closure + rules and unchanged-workload boundaries. +2. Write and independently review an ordered implementation plan with exact + files, owner tests, full-suite commands and any proposed charged derivation. +3. Publish both documents in one non-draft planning PR when authorized. Use + `Refs #33`, `Refs #50` and `Refs #48`, not closing keywords. +4. Merge the approved planning PR only with the user's merge authorization. + Then implement in a separate worktree and PR from the updated default + branch. No implementation is authorized by starting this planning cycle.