Skip to content

fix(storage): stream growth measurements and preserve registry errors - #4794

Open
ScriptedAlchemy wants to merge 67 commits into
masterfrom
fix/4099-report-registry-presence-20261107
Open

ScriptedAlchemy wants to merge 67 commits into
masterfrom
fix/4099-report-registry-presence-20261107

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Nov 8, 2026 •

Copy link
Copy Markdown
Owner

Scope

Refs #4099. This remains an issue-specific partial repair, not a claim that the deleted 153 GB profile has been reconstructed or that the 24-hour master acceptance has passed.

Measurement-path root cause and repair

The actual reader-worker TableSizes command joined one dbstat row per database page to sqlite_schema, then used GROUP BY to recover one total per table. That creates a temporary grouping sorter proportional to the number of pages being measured and consumes avoidable SQL VM work inside the existing measurement deadline. Large-store measurement was doing expensive page sorting in addition to the necessary page walk.

The current reader initializes one total per user table from sqlite_schema, then walks SELECT name, payload FROM dbstat once on the same snapshot. It includes empty and rootless user tables, excludes index and internal pages from their totals, and returns an interruption or arithmetic error before publishing results. The earlier correlated per-table SUM at 8b403d1 was an intermediate implementation.

The existing registry fixes are preserved: the offline report distinguishes true global.db absence from metadata/read errors and refuses registry symlinks rather than importing foreign registration identities into a profile whose census cannot account for those linked bytes.

Behavioral regressions

Three new filesystem-backed tests call the production census implementation:

  • Exact equality with an independent per-page reference on nonzero overflow payloads, WITHOUT ROWID storage, an empty table, and a rootless virtual table. Index/internal-table bytes are not misattributed. A subsequent insert raises the live measurement; deletion lowers it while physical page count stays high; vacuum of only the disposable test fixture lowers page count without changing live payload totals.
  • The same nonzero fixture measures actual VM work for both implementations and requires the production census to use less than half the old grouped query's work. Restoring the old query makes this regression fail. No wall-clock threshold or source/query-plan string assertion is used.
  • An interrupted real SQLite census returns an error, never partial/zero success, and the same connection successfully retries with the exact nonzero sizes.

The earlier dangling/valid-external-registry behavioral tests remain in the PR.

Validation and review

  • A separate executable SQL reproduction using SQLite 3.46.1 returned identical payload totals on a 1,040-page fixture: 19,043 VM steps before, 6,577 after. Its events table contained 4,194,384 measured payload bytes. Deletion/compaction accounting and interruption/retry checks passed in this reproduction. These are SQL mechanism results, not a repository Rust test pass.
  • Original worker source was materialized from the immutable GitHub revision and matched blob 9edee1472e796e6bde5bc1564b49a452c56e75d0 before editing. Published blobs match the locally inspected source. Comparison against prior head 43cef92 shows only the production dispatch change and the new census/test module; the concurrent formatting fix is preserved.
  • Published through a non-force fast-forward with exact expected old head 43cef92bc121f1bf9b451de1a3b92c8e40fd624f.
  • This runner has no Bazel/toolchain or full checkout, so no local Rust compilation, formatter or Rust test pass is claimed. Hosted Repository gates on 8b403d1 found only pinned-rustfmt differences in the new test module; the exact reported formatting corrections were published as d9559c8 using a non-force fast-forward with expected old head 8b403d1. The new-head CI run #42437543266 is queued; the old run's Rust jobs were cancelled by supersession, not passed. Required validation on the exact head:
    bazel test //crates/tracedecay-rusqlite-runtime:unit_test //crates/tracedecay-maintenance:unit_test --test_output=errors
    bazel build //crates/tracedecay-rusqlite-runtime:rustfmt_check_2024 //crates/tracedecay-maintenance:rustfmt_check_2024
  • No Cargo, delegation, policy/budget increases, new dependencies, CI changes, operator-data reclamation, release or infrastructure changes. No files from held fix: consolidate follow-up application issue repairs #4776 are transplanted or modified.

Still required before closing #4099

This removes a demonstrated source of measurement work amplification; it does not make the physical page walk constant-time or prove that every large store fits its deadline. Per-family bounds, daemon-owned retention/reclamation and the fresh-profile 24-hour normal-use acceptance must still be demonstrated on master. Keep #4099 open; keep Kev/decision-model work and mixed #4776 draft and excluded from merging.

Current CI repair (679f636)

On the prior head d9559c8, pinned Repository gates passed. Clippy failed on storage_report.rs:2492 with clippy::assert_is_empty (-D warnings): the added test used assert!(absent.stores.is_empty()). Commit 679f636 replaces that with a typed assert_eq!(absent.stores, Vec::<StoreSizeReportEntry>::new()), retaining the failure check and adding value-level diagnostic output. Published via exact-head expected_sha=d9559c8, force=false, with no concurrent commits overwritten.

The earlier fleet Linux job failed with UNAVAILABLE: io exception from the external Bazel remote executor (not a repository test assertion). The earlier Clippy job additionally observed that transient executor failure. Fresh hosted CI is pending on 679f636. No Bazel executable or Rust toolchain is available locally, so the current Rust test and Clippy results remain unverified. Do not claim passing gates, merge, or #4099 closure until actual results and master acceptance exist.

Partial WAL findings

The long-key WAL review and its duplicate are addressed in 55ef65ea44. When Doctor hashes a footprint reference, its coverage statement now retains the storage family and the unreadable-entry count. Both the measured-byte floor and the configured size bound remain visible.

Native macOS Bazel passed all 10 footprint tests and the repository formatting check. The WAL regression walks a real regular segment and an unreadable directory entry, then verifies both a proven overage and a partial finding below the bound. Linux exercises a key over 1,024 bytes. The native macOS path uses a control character to trigger the same reference fallback within the filesystem path limit. Exact-head Linux remains pending.

Nested graph sidecar accounting

At 4b542edc22, the project Doctor bound and the physical storage report use the same classifier for the container file and its recognized sidecars. Both count a spill's nested files, including an orphaned spill directory, against its named container. Unrelated suffixes and unrelated directories with a .grafeo name stay outside the container bound. A dedicated WAL retains its separate bound.

On the preceding head, replacing the flat orphaned spill fixture with a real .grafeo.spill/scratch/inflight path made the Doctor finding disappear. The adjusted case now reports the exact nested bytes and a degraded container bound. Both whole macOS Bazel unit suites passed on the repaired head, with 176 maintenance and 431 rusqlite runtime cases, plus repository rustfmt and strict Clippy on the maintenance test target. Current-head hosted CI, independent review and the separate 24-hour master acceptance for #4099 remain open.

@changeset-bot

changeset-bot Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: 830e9cb

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Refs #4099. Aggregate each table's dbstat payload inside a correlated
subquery instead of materializing all pages for GROUP BY. Preserve the
existing reader snapshot, cancellation, deadline and typed-error path.

Add nonzero filesystem-backed regressions for exact payload attribution,
growth, logical deletion versus physical compaction, VM-work reduction,
and interruption followed by a successful retry. No retention policy or
measurement budget is relaxed; full 24-hour profile acceptance remains open.
@ScriptedAlchemy ScriptedAlchemy changed the title fix(storage): distinguish unreadable registry from absent registry fix(storage): stream growth measurements and preserve registry errors Nov 8, 2026
Refs #4099. Apply the exact rustfmt delta reported by the hosted
Repository gates; no production logic or acceptance criteria changed.
Refs #4099. Use a value assertion so the hosted Clippy
assert_is_empty policy can display an unexpected store on failure.
Refs #4099. LIKE treats the underscore in sqlite_% as a wildcard,
silently excluding valid user tables such as sqlitemessages. Match the
literal reserved sqlite_ prefix instead without changing the streaming
page census or its cancellation and snapshot boundaries.

Add a file-backed regression that requires the omitted table's nonzero
payload and subsequent growth to match an independent dbstat sum while
still excluding sqlite_sequence. Preserve the concurrent CI repair.

Copy link
Copy Markdown
Owner Author

Published a3654ac directly to this PR through GitHub (Refs #4099). The concurrent CI repair 679f636 is preserved as its parent; the branch update used that exact expected old head with force=false.

Production bug fixed: the census excluded LIKE 'sqlite_%'. _ is a wildcard there, so a valid user table such as sqlitemessages silently disappeared from the table-growth measurement. The production filter now uses NOT GLOB 'sqlite_*' to match the literal reserved prefix. Snapshot ownership, streaming aggregation, cancellation, deadlines, and physical-footprint accounting are unchanged.

Regression added: the file-backed Rust test calls the production reader, requires a nonzero sqlitemessages measurement, inserts more payload, checks its exact total against an independent named-table dbstat sum, and confirms that the real internal sqlite_sequence table remains excluded. The prior three regressions remain unchanged.

Executed SQL verification: using SQLite 3.46.1 on temporary files and SQL extracted from the candidate source, the old filter omitted 1,048,581 payload bytes; the new filter reports them and subsequently reports 2,097,162 bytes after another insert. The original fixture still has exact reference parity and 6,577 versus 19,043 VM steps. Interruption returns SQLITE_INTERRUPT, and retry returns the complete exact result. These are actual SQL checks, not a Rust/Bazel test pass.

The uploaded source blob f24bfed629aca65edaaca2b120a10dca1e798eba matches the locally inspected candidate; the separately reviewed GitHub diff is exactly one file, +39/-1. No code from held #4776, CI configuration, release files, production data, or other task branches was changed.

Fresh exact-head CI: https://github.com/ScriptedAlchemy/tracedecay/actions/runs/42442890231 (queued at verification). Local Bazel and Rust toolchain are unavailable. Require the real current-head Bazel/Clippy/format results before merging. This is a bounded correctness fix, not evidence that the original 153 GB incident or #4099's 24-hour master acceptance is resolved.

Refs #4099. Align the new user-table growth assertion with the
exact format expected by the Bazel rustfmt gate; no behavioral changes.

Copy link
Copy Markdown
Owner Author

Published 4bcc943de6ecf4cc84243cb3beed2e87d8d38a2e directly to this issue-specific PR (Refs #4099), fixing the exact pinned-rustfmt failure on prior head a3654ac.

The Repository gates log showed only a formatting difference in the new sqlitemessages payload-growth regression at reader/worker/table_sizes.rs:211. I applied the formatter's exact multiline assertion, without changing SQL, tests' semantics, limits, or CI. The commit was independently diff-checked (one file, +5/-1) and published with expected_sha=a3654ac, force=false; concurrent branch changes remain intact.

On a3654ac, Clippy passed. The full Linux fleet suite failed two unrelated targets: //crates/tracedecay:automation_runner_test (three backend_unconfigured vs scheduler-expected assertions) and //crates/tracedecay:runtime_acceptance_suite (LSP frame acknowledgment DeadlineExceeded). Neither belongs to the three-file #4794 patch; I did not alter their owners' code or weaken tests. Fresh CI on the exact new head was queued at verification. This environment has no Bazel executable or repository checkout: no local Rust-test pass is claimed, and full suite acceptance is still blocked.

The master baseline is unchanged at 91c72ab; this remains a partial measurement-path repair, not a 24-hour fresh-profile bounded-growth/reclamation proof. Keep #4099 open, and leave mixed #4776 draft and release #4763 untouched.

Refs #4099. The offline registry snapshot followed symlinked WAL/SHM
members and could copy arbitrarily large foreign files into scratch.
Refuse nonregular members before copying, and keep physical family byte
counts from charging symlink targets outside the inspected profile.

Add real offline-report and family-copy regressions for symlinked source
members, plus partial-coverage and size assertions for user sessions.
No operator data is copied or deleted.

Copy link
Copy Markdown
Owner Author

Published 1e3d62f (Refs #4099), direct to this issue-specific PR. One-file update in crates/tracedecay-maintenance/src/retention/storage_report.rs; the parent is exactly 4bcc943 and the PR branch advanced with expected_sha=4bcc943, force=false. No peer branches or held #4776 were changed.

New root-cause finding: the offline storage report copies the registry's SQLite main/WAL/SHM family into temporary scratch. Its sidecar check used Path::is_file(), which follows a symlink. A linked global.db-wal or -shm could therefore import arbitrarily large foreign bytes into the report scratch before the diagnostic runs. Separately, database_family_bytes used following metadata(), misattributing a foreign linked file as profile-owned physical storage, while the canonical footprint walker refuses that link. This contradicts the report's read-only, profile-scoped accounting.

Fix: fail closed on every nonregular/linked snapshot member via symlink_metadata before copying any member; use no-follow metadata for the physical SQLite family byte tally. The full profile census still marks linked entries unavailable rather than fabricating complete coverage. No reclamation or operator data mutation.

Behavioral regressions: a real on-disk SQLite registry with a symlinked WAL and SHM, each targeting a sparse 512 MiB foreign file, must reject copying before a destination is created; the end-to-end offline report must return a typed Database failure for a linked registry WAL; and an offline report with a regular 4096-byte user-sessions.db plus a foreign linked WAL must count exactly 4096 owned bytes and report partial profile coverage. The foreign files' metadata must remain unchanged. These tests exercise production functions and are not source-shape checks.

Independent checks: manually reproduced the original stat/is_file symlink-follow behavior on a disposable SQLite fixture with a 1 MiB foreign WAL; independently reviewed the uploaded GitHub commit patch and parent comparison (one file, +140/-7). This is mechanism evidence, not a Bazel/Rust test pass. This environment lacks Bazel and the Rust toolchain. Hosted exact-head CI: https://github.com/ScriptedAlchemy/tracedecay/actions/runs/42458659562 (pending at check). Require Repository gates, Clippy, and nonzero Bazel //crates/tracedecay-maintenance:unit_test / //crates/tracedecay-rusqlite-runtime:unit_test before merge.

This is partial correctness only; #4099 remains open until 24-hour fresh-profile, multi-store bounds and daemon-owned reclamation are proved on master. No release/infra/CI changes. No transplant from mixed draft #4776.

Refs #4099. Apply the exact formatter hunk reported by the hosted
Repository gates on 1e3d62f. Preserve source behavior and all assertions.

Copy link
Copy Markdown
Owner Author

Published 8913817dbe908e6f27c0bcd317ac8f6c683679e6 on this issue-specific PR (Refs #4099).

The hosted Repository gates on 1e3d62f reported exactly one pinned-rustfmt diff in the new symlinked-SQLite-sidecar regression (storage_report.rs:2571): the symlink(...).unwrap() expression needed one line. Applied that exact formatter change only; all production source, behavioral assertions and limits are preserved. The commit changes one file (+1/-2) and was published as a non-force fast-forward using expected_sha=1e3d62f5c2c687df2cae16e5791416291ef29e97, preserving concurrent work.

Fresh exact-head CI: https://github.com/ScriptedAlchemy/tracedecay/actions/runs/42462849057 (queued at check). The previous run's Repository gates failed only on this formatting hunk; the Linux/Clippy jobs were still queued there at review time and are not evidence of a pass. There is no local Bazel executable or supplied checkout, so I do not claim a local Rust test pass. Require passing real nonvacuous //crates/tracedecay-maintenance:unit_test, //crates/tracedecay-rusqlite-runtime:unit_test, formatting and Clippy gates before merging.

The master accepted #4123 footprint implementation remains in place. #4099's 24-hour fresh-profile per-store bound and daemon-owned reclamation acceptance remains unproven on master; keep it open. Held mixed #4776 remains draft; release #4763 and other issue-owner branches were untouched.

devin-ai-integration[bot]

This comment was marked as resolved.

Refs #4099. The SQLite snapshot enumerated sidecars before capturing
its validation generation. A writer creating a WAL in between could
commit new project registrations that the report then misclassified as
unregistered. Capture the generation first, then inspect only regular
members and validate after copying.

Add a deterministic on-disk WAL race regression: stale generations must
fail closed, while a fresh generation preserves the committed project.
Also fix the pinned Clippy collapsible-if failure in family sizing.
Refs #4099. The registry snapshot preflight rejected symlinks, but
fs::copy reopened each checked path and could follow a link swapped
in after inspection, copying foreign bytes into report scratch.
Open through a held parent capability with no-follow leaf policy,
then copy from that file handle. A real filesystem regression swaps
a checked WAL member to a foreign link and proves the copy refuses
without creating a scratch member. Preserve existing CI repairs.

Copy link
Copy Markdown
Owner Author

Published f9827683e484336fcd6d7fd752084f6eb85932f0 on issue-specific PR #4794 (Refs #4099), by exact expected-old-head eb751efd4e5c4db616b65cd7b5ab52bc4ad237f0, non-force fast-forward.

New independently identified correctness/safety issue: The offline SQLite family copier checked each source member with symlink_metadata, then used fs::copy to reopen by pathname. A WAL/SHM member replaced with a symlink after preflight could make the report copy foreign bytes into scratch, despite the earlier link guard. The fix opens each checked member through a held parent directory with FollowSymlinks::No, copies from that file handle, preserves the generation validation and current deadline/snapshot ownership, and changes no CI/retention limits or dependencies. A new filesystem-backed Rust regression replaces a prechecked WAL member with a symlink and requires refusal with no copied destination WAL; reversing the production no-follow open would fail it. One-file diff +61/-3.

The Linux filesystem TOCTOU mechanism was independently reproduced using a regular-file probe followed by symlink replacement: ordinary copy followed the foreign target while O_NOFOLLOW refused it. This is not a Bazel/Rust test pass. No local Bazel/toolchain was available; exact-head hosted CI is queued. Keep #4099 open: 24-hour fresh-profile per-family bounds and daemon-owned reclamation remain unproven on master. Mixed #4776 remains draft, release #4763 untouched, and no other owner branch or production state was modified.

Refs #4099. A no-follow open still accepts a regular-file replacement
between preflight and copy, potentially copying a huge foreign WAL before
post-copy generation validation rejects it. Compare the opened descriptor
against checked identity and length before creating scratch; bound the copy
to the inspected length plus one. Add a filesystem regression for same-sized
and oversized regular-file swaps without reading the foreign bytes.

Copy link
Copy Markdown
Owner Author

Published 2d839d8 directly to this focused PR (Refs #4099). The PR was fast-forwarded non-force with exact prior head f9827683e484336fcd6d7fd752084f6eb85932f0; earlier repairs and concurrent work were preserved.

Independent bug found: The preceding no-follow fix rejects symlink replacement but still opens an ordinary file swapped into the WAL/SHM pathname after the first metadata check. Post-copy SourceGeneration::validate would reject the changed path, but only after the report may have copied an arbitrarily large unrelated regular file into scratch. Merely checking regular-file type or matching length cannot reject a same-sized inode swap.

Production repair: Preserve checked metadata for each SQLite main/WAL/SHM member; compare the opened, held file descriptor's identity and inspected length before creating the scratch member (Unix device/inode; non-Unix conservative size/time checks with existing generation validation). Cap each copy at the previously measured length plus one byte and reject a concurrent size change, rather than chasing an unbounded WAL tail. No dependencies, CI policy, thresholds, retention ownership, or production data were changed.

Nonvacuous Unix regression: Replaces a previously checked, 4 KiB regular WAL with a different regular inode of equal size and again with a 512 MiB sparse file. It calls the production copy helper, requires a typed InvalidData refusal and no output WAL for either case, and verifies the replacement files remain unchanged. Removing the new identity guard allows the old implementation to copy the replacements. Existing link-swap and generation-change regressions remain.

Independent filesystem reproduction: On Linux, rename of a different same-sized regular inode leaves is_file and length checks true, but the checked versus opened device/inode differs. This confirms the mechanism, not a repository Rust test pass. This runner has no Bazel checkout/executable. Fresh hosted CI on the exact new commit is queued; require real nonzero Bazel tests, pinned rustfmt and Clippy before any merge. The 24-hour fresh-profile #4099 acceptance remains unproven on master; do not close. Mixed #4776 stays draft and release #4763 is untouched.

devin-ai-integration[bot]

This comment was marked as resolved.

Refs #4099. Keep inode identity verification unchanged while moving
MetadataExt to the module's explicit imports as required by AGENTS.md
and the open review on PR #4794.
Refs #4099. A rollback-mode writer may spill uncommitted registration
pages into global.db while the undo journal remains. Copying only the
main/WAL/SHM family would turn those pages into false registry truth.

Fail closed on the rollback journal before writing scratch. Add direct
and end-to-end real-SQLite regressions for refusal during an open
transaction and correct registration after rollback. Preserve all
prior snapshot generation and no-follow protections.
devin-ai-integration[bot]

This comment was marked as resolved.

Copy link
Copy Markdown
Owner Author

Published focused Refs #4099 correctness fix on PR #4794: commit 32aa388.

Root cause: the offline registry reporter copies only global.db, -wal, and -shm. Under SQLite rollback-journal mode, an in-flight writer can spill uncommitted pages into the main file while its undo journal remains at global.db-journal. Copying the main file without that journal can make uncommitted project registrations look authoritative. An independent SQLite 3.46.1 reproduction on disposable files demonstrated a main-only snapshot reading /repos/uncommitted while the open writer later rolled back to /repos/original….

Published repair: refuse a present rollback journal before writing snapshot scratch and propagate the existing typed report failure instead of classifying stores using an unsafe registry. Two real-SQLite Rust behavioral regressions test the lower-level snapshot refusal (including no output) and the end-to-end build_storage_report refusal followed by correct registered-store reporting after rollback. This is intentionally conservative for persistent-journal configurations; it does not delete or checkpoint any live journal.

The exact-head non-force fast-forward preserved 7f54a84 as parent. Verified GitHub commit diff: one issue-owned file (storage_report.rs), +98/-0; no CI, release, infra, production data, #4776 or sibling branches touched. Bazel/Rust testing is unavailable locally, so no Rust test, Clippy, or formatting pass is claimed. Fresh exact-head CI: https://github.com/ScriptedAlchemy/tracedecay/actions/runs/42484464871 (queued on inspection). Keep #4099 open: the 24-hour fresh-profile multi-store bounded-growth and daemon-owned reclamation acceptance has not been proven on master.

Apply eight exact Rust 2024 rustfmt differences from hosted Bazel
Repository gates. No behavior, schema, or test assertion changes.
Refs #4099

Charge nested WAL-looking host bundle and component filenames to their
top-level directory bound, not an unrelated WAL key. Check exact physical
bytes and named Doctor overages in a real filesystem regression, keeping
genuine top-level WALs independent. Apply the pinned rustfmt corrections
reported by exact-head CI.
Rename the predicate used by project kind accounting so it cannot
shadow the SQLite path constructor used by offline registry snapshots.
Restore Bazel Rust compilation without changing family classification,
snapshot handling, or the existing filesystem-backed regressions.

Refs #4099
The Doctor project overlay must only promote WALs rooted in the project
store. Scratch files inside indexed artifacts, LCM payloads and response
handles remain charged to the project, not unrelated WAL findings.

Add a real profile filesystem census regression for exact bytes,
project-kind attribution, and over-budget vs healthy Doctor evidence.

Refs #4099
Only regular root-level WAL files and actual Grafeo WAL directories
receive WAL bounds. Keep similarly named incident trees separately
bounded and preserve genuine graph WAL pressure findings.

Add real filesystem-backed Doctor and storage-kind regressions.

Refs #4099
devin-ai-integration[bot]

This comment was marked as resolved.

Refs #4099. A nested .wal payload is not a WAL family after
root-only classification; test bounded evidence-key behavior directly.
Refs #4099. Preserve measured bytes but mark the missing store
traversal unavailable and apply the unknown-file bound. Exercise
normal and overbound replacements through the real footprint census.
Refs #4099. Test the degraded threshold using an explicit
small limit while retaining the independent default-bound assertion.
Refs #4099. Preserve the concurrent freelist-aware retry fix and its
regression. Add a real maintenance-registry journey proving unchanged
work remains suppressed, logical deletion retries without compaction,
old watermarks survive that retry, and subsequent growth is measured.

Seed the existing deadline transition deterministically rather than
relying on host speed. Keep production policy and measurement limits
unchanged.

Copy link
Copy Markdown
Owner Author

Review and published regression follow-up — Refs #4099

Published 6179c8b on top of the concurrent production fix 95e7ad1, preserving that fix and its existing regression. The update used expected_sha=95e7ad1da297972c5be7bd5e997c0f4237c9419f, force=false; the verified PR head is now 6179c8b. My earlier candidate based on 7de8bcd was not pushed over the concurrent update.

Root cause independently reproduced and production fix reviewed: timeout suppression compared allocated page_count, although dbstat does not visit freelist pages. Ordinary retention can free most table pages without shrinking the file. Comparing page_count - freelist_pages therefore allows a cheaper measurement immediately after logical reclamation while unchanged work still respects the existing suppression interval. Checked subtraction preserves a typed unavailable result for inconsistent counters. Physical size accounting, timeouts, admission and GC policies are unchanged.

New behavioral coverage: telemetry/reclaim_tests.rs runs through the real runtime writer and StoreTelemetrySamplingRegistry::advance_registered, not a source-text check. It establishes a populated baseline, seeds the existing production timeout transition deterministically, verifies unchanged work is suppressed, deletes all but one payload without VACUUM/restart, requires a successful retry against the original timestamp and nonzero retained payload, then inserts again and requires positive measured growth from the recovered baseline. This complements the concurrent test's no-baseline/full-deletion case. The new test is wired into the existing Bazel unit target through its recursive source glob. No existing assertions were weakened.

Executed SQLite mechanism evidence (3.46.1, disposable filesystem only): before deletion: 2,054 allocated pages, 0 freelist pages, 2,054 dbstat rows, 8,388,736 payload bytes. After deletion: still 2,054 allocated pages and the same 8,413,184-byte file, but 1,988 freelist pages, 66 dbstat rows and 262,148 payload bytes. A subsequent insert produced 524,296 payload bytes without changing the physical file size. The old retry guard stayed suppressed; the corrected guard admitted the retry. This reproduction is not a repository Rust test pass.

Validation: Linux fleet/Test Linux, Clippy and Dashboard passed on the preceding f0d9914 CI run #42710315413; its formatting failure was already corrected by 7de8bcd. Exact-head CI for this publication is https://github.com/ScriptedAlchemy/tracedecay/actions/runs/42723235152 (queued at verification). This execution environment has no Bazel/Bazelisk or Rust toolchain, so the new Rust regression, formatting and Clippy remain unverified until hosted results arrive. Relevant checks are bazel test //crates/tracedecay-maintenance:unit_test --test_output=errors and bazel build //crates/tracedecay-maintenance:rustfmt_check_2024, followed by the unchanged required gates.

Keep #4099 open: fresh-profile 24-hour host acceptance and full master-proven reclamation are still outstanding. #4776 was verified open/draft and left untouched; no release, production, infrastructure, other branch or schedule changes.

Copy link
Copy Markdown
Owner Author

Validation update for published head 6179c8b: Repository gates in run #42723235152 has now failed, at Test repository policies and check formatting with Bazel. This supersedes the queued status in my preceding comment. The preceding head's Linux/Clippy passes are not current-head acceptance.

The available log response is truncated before the failure diagnostics, and the connection does not expose a readable annotations/log-range action. I have not verified whether this is rustfmt or another policy failure and will not claim an exact formatting repair without those diagnostics. There is no local Bazel/Bazelisk or Rust toolchain in this execution environment. The new baseline-preservation/regrowth regression is published but not yet validated by repository Rust tests; this PR is not ready to merge on the evidence available here.

The concurrent production fix 95e7ad1 and its direct regression remain intact. 6179c8b adds the real maintenance-registry journey without changing production policy. Keep #4099 open; no held draft, release, production, infrastructure or schedule was changed.

Refs #4099. Match the three exact rustfmt hunks reported by Repository
gates on the reclamation regression. No production behavior, test
expectations, or CI policy changes.

Copy link
Copy Markdown
Owner Author

Refs #4099: Published exact pinned-rustfmt repair aaf3e6f after reviewing complete Repository gates logs (exactly three formatting hunks, eight policy tests passed). Two Rust files changed, no assertions/behavior or CI policy changed; one descendant, expected-head nonforce fast-forward. Fresh exact-head CI queued. Bazel Rust unit tests and Clippy still unverified, and #4099 host/master acceptance outstanding; do not merge or close yet.

Copy link
Copy Markdown
Owner Author

Refs #4099 — exact-head Bazel qualification update (2026-11-10 03:30 UTC). New CI blocker: run #42727153286, Linux fleet failed 1 of 183 Bazel test targets. I downloaded the published bazel-test-logs-Linux-fleet Actions artifact and inspected the actual test logs, rather than inferring a cause from the truncated job console.

Relevant #4099 Rust tests PASS on published head aaf3e6f: //crates/tracedecay-maintenance:unit_test 188/188 (29.09 s), including both telemetry::reclaim_tests::logical_reclamation_retries_a_timed_out_census_without_shrinking_the_file and telemetry::table_growth_tests::freed_pages_retry_a_timed_out_census_without_file_compaction; //crates/tracedecay-rusqlite-runtime:unit_test 440/440 (23.96 s). Repository gates, Clippy, Dashboard and Feature gates also passed on this head.

Sole Linux fleet failure: //crates/tracedecay:unit_test 795 passed, 1 failed, specifically daemon::production_harness::configuration_protected_preview_journey_test::protected_preview_redacts_the_change_and_refuses_stale_or_invalid_input. The test panicked at crates/tracedecay/src/daemon/production_harness/configuration_protected_preview_journey_test.rs:169: production-composition code index did not publish ... after 20000 ms; diagnostic reports composition gate capacity=4, admitted=4, waiting=0, rebuild_in_flight=true and CodeGraphServing::Unavailable(generation_unavailable). That is a code-index/production-harness readiness failure; no evidence yet attributes it to the footprint or table-growth repair. This test is outside the seven files changed by this issue-specific PR; I have not raised its deadline, weakened assertions, changed CI, or overwritten another issue owner's work.

Hosted standalone Linux and Windows lanes are not complete at this checkpoint. No local Bazel/Bazelisk executable or checkout is present here; this is hosted Bazel Rust evidence, not a local run. #4794 remains open/unmerged and must not be qualified while required CI is red. #4099 remains open because daemon-owned reclamation and the specified 24-hour fresh-profile bound on master remain unproven. Mixed decision-model #4776 stays draft and untouched.

Refs #4099. Directory enumeration follows symlinks when the walk root
itself is linked, even though child entries use no-follow metadata.
Check each pending directory before enumeration, keep its unreadable
coverage explicit, and never attribute the linked target's bytes to
the requested profile.

Add a filesystem-backed regression: the linked profile reports a partial
zero-byte floor without foreign evidence; the directly selected real
root reports the exact 2048-byte overage. Preserve existing entry budgets
and cancellation behavior.

Copy link
Copy Markdown
Owner Author

Refs #4099 — published additional no-follow footprint fix 3610e38 on this existing issue-specific PR.

Reproduced defect: std::fs::read_dir(path) traverses a symlink if the path itself is linked. The footprint walker checked DirEntry::file_type for symlinked children but did not verify a caller-supplied walk root or queued directory immediately before reading it. A linked profile root could therefore count an unrelated directory's bytes as if they belonged to this profile, contrary to the no-follow census contract. A disposable filesystem reproduction confirmed this read_dir versus symlink_metadata distinction.

Focused correction: require each pending directory to be a real directory via no-follow symlink_metadata before read_dir. A linked/unavailable root is counted as unavailable coverage instead of crossing into its target; the entry ceiling and cancellation checkpoints are unchanged. This check closes the stable linked-root case, not arbitrary adversarial swaps between the check and enumeration.

Nonvacuous regression: linked_profile_root_cannot_measure_foreign_storage_as_owned sets up a real symlinked profile to a separate 2,048-byte payload tree. The linked census must count 0 foreign bytes, record 1 unavailable root, and report profile partial, not healthy. Directly choosing the real directory must count 2,048 bytes and issue the named degraded LCM bound finding at a 1-byte override. The pre-fix walker fails these conditions.

Publication reviewed against the GitHub commit diff: one descendant of aaf3e6f, exactly one changed Rust file (+49 lines), exact-old-head non-force fast-forward with expected_sha=aaf3e6fa2349e2a9afee5e38d6157f7ecf2213da; no peer branch overwritten. New-head CI #42768497322 is queued. The execution environment has no Bazel/Bazelisk or Rust compiler, so no current-head repository Rust, format, or Clippy pass is claimed. The earlier Linux root-suite protected-preview readiness failure belongs to a separate owner and was not modified.

Keep #4099 open and this PR unmerged: the required 24-hour fresh-profile host observation and complete daemon-owned reclamation on master remain unproven. Held #4776 remains draft; no release, production, infrastructure, tags, credentials, Kody/Copilot/delegation, or CI policy changes.

Refs #4099. The profile footprint census refuses a linked root, but
the offline registry reader and per-project report still resolved child
paths beneath that link and sampled foreign storage. Reject the linked
profile authority before registry reads, paged reports or project sizing.

Cover the actual offline and project report paths with a filesystem
symlink and foreign payloads; explicitly selecting the real directory
continues to report exact measured bytes.
Refs #4099. Seed a real foreign SQLite registry alongside the external
project and sessions bytes in the linked-profile refusal journey. The
explicit real-root control must report the registered project and exact
payload bytes, proving the negative case is not vacuous.

Copy link
Copy Markdown
Owner Author

Refs #4099 — published follow-up to the no-follow footprint fix on this existing issue-specific PR.

  • 607a496: the newly corrected footprint walker refused a symlinked profile root, but build_storage_report still entered that same linked root to copy global.db, and project-scoped/paged reporting could still measure foreign storage through the linked path. Applied a shared no-follow profile-root guard before offline registry reads, daemon paged sampling, or per-project sampling; invalid/linked roots fail with an actionable typed error instead of misattributing target bytes. No storage is deleted.
  • 830e9cb: strengthened the actual Rust filesystem regression with a real foreign SQLite registry row, registered project containing 16 bytes, and 4,096-byte user-sessions file. Both offline and project report paths must reject the linked root; explicitly selecting the real directory must still return the registered project and exact 16/4,096-byte measurements. Before the fix the offline report returned a foreign report instead of refusing, so the regression is not vacuous.

Independent local POSIX reproduction verified why a child-path symlink_metadata is insufficient: the direct profile component can be a symlink while its global.db/sessions descendants look like ordinary files. The production source, existing tests and master implementation were reviewed; master has no direct changes to the seven issue-specific paths since this PR's merge base. No Bazel/Bazelisk exists in this runtime, so no repository Rust test pass is claimed on this new head; current-head CI #42772419833 was queued at verification.

Verified exact-old-head non-force fast-forwards 3610e38 → 607a496 → 830e9cb. The new diff is one file, +63/−0, and retains all peer commits. No Cargo, delegation, CI weakening, branch rewrites, production/infra/release/tag/credential writes or changes to other owner PRs. Held #4776 remains draft and unmerged.

Still partial: no master-proven daemon reclamation completeness or 24-hour fresh-profile bounded-growth acceptance; #4099 remains open. Root-link checks close stable linked roots and do not claim to solve adversarial rename/symlink races between metadata inspection and later path access.

This branch has not been deployed

No deployments
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