Skip to content

fix(graph): mark collected only after sealed retirement - #4818

Closed
ScriptedAlchemy wants to merge 3 commits into
masterfrom
fix/4616-sealed-retirement-state-after-unlink
Closed

ScriptedAlchemy wants to merge 3 commits into
masterfrom
fix/4616-sealed-retirement-state-after-unlink

Conversation

@ScriptedAlchemy

@ScriptedAlchemy ScriptedAlchemy commented Nov 8, 2026 •

Copy link
Copy Markdown
Owner

Root cause and focused repair

Refs #4616 — partial retention-state correctness fix, not closure of the sealed-generation collection acceptance.

GraphDb::delete_generation_contents deleted staging rows, then marked the locator collected and cleared its in-memory retirement state before calling retire_sealed_generation_store. If a real filesystem unlink failed, the method returned an error but the in-memory collected set already claimed the on-disk sealed generation was gone. This is an unsafe ordering between physical reclamation and in-memory completion state.

Move sealed retirement before the transition to collected, preserving the prior idempotent error/retry semantics. The existing real filesystem test intentionally places a regular file in the exact sealed-artifact directory slot to force a removal error; its new assertions require the locator not collected after failure, collected only after successful retry, and physical directory removal. The pre-change code fails the first new assertion, so the regression is not vacuous.

Scope: one file, crates/tracedecay-graph-db/src/generation_runtime.rs (21 additions, 1 deletion). No dependency, CI, timeout, release or infrastructure modifications. Base master = 91c72aba7750a398dd28e83d64bf0c6d6fef4172, isolated source commit d53b9e35017802c04d51f1e3cfd7ae58425a1ecb. Created its issue-specific branch from the exact base and published via expected-old-head force=false fast-forward, then verified the remote ref and 1-file compare.

Required validation and boundaries

No native Bazel/macOS pass is claimed: this tool environment has no checked-out repository or Bazel installation. Run bazel test //crates/tracedecay-graph-db:unit_test --test_output=errors, ensure the named regression actually executes (nonzero tests), and review applicable CI before merging. No Cargo was run.

The separate #4804 addresses receipt/ownership cleanup, #4809 addresses bounded active-pointer reads, #4813 addresses fair writer admission, and #4799 addresses Doctor reclaim guidance. None is merged into master; this PR does not import their work or touch their paths. Mixed draft #4776 and Kev/decision-model PRs must remain draft and never merge or auto-merge. Do not close #4616 until complete N>3 sealed-retirement, unsafe-artifact isolation, bounded progress under writers, Doctor guidance, and OOM-safe storage-report acceptance are all proven on master. Release #4763 remains read-only; no production/infra/tags/credentials changes.


Devin Review

Generation deletion could commit staging-row removal and mark the in-memory
locator collected before attempting sealed-artifact retirement. An unlink
failure then returned an error with the durable sealed bytes still present,
while the in-memory collected set falsely declared the generation gone.

Retire the sealed artifact before transitioning the locator to collected.
Strengthen the existing real filesystem regression with negative and
positive collected-state assertions across failure, retry and success.

Refs #4616
@changeset-bot

changeset-bot Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: d89da71

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

@devin-ai-integration devin-ai-integration Bot left a comment •

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note

Newer findings are available below. Devin Review posted a newer report on this PR, in addition to the findings presented here.

✅ Devin Review: No Issues Found

Devin Review analyzed this PR and found no bugs or issues to report.

Devin Review

The successful retry fixture previously created an arbitrary directory with
no receipt. The ownership-verified sealed retire path correctly refuses such
an artifact, so combining the safe collector with this regression would
fail even though collection is correct.

Use a self-describing, generation-bound receipt in the retry, preserve an
unrelated sibling sentinel, and require typed sealed-retirement failure
without hard-coding the former unsafe remover's exact message.

Refs #4616

Copy link
Copy Markdown
Owner Author

Refs #4616 — published b3a13ddb directly onto this issue-specific PR via expected-old-head d53b9e35017802c04d51f1e3cfd7ae58425a1ecb, force=false. Remote PR head and REST compare independently verified: one file, generation_runtime.rs (+35/-3); concurrent branches untouched.

Root cause (cross-PR behavioral incompatibility): the real failure/retry regression used a synthetic commit-metadata.sealed/<generation>/derived-rows directory without sealed.json as the successful retry case. The independent ownership-safe collector in #4804 rightly requires a bounded, exact identity-matching receipt before deleting a directory. If both issue-specific changes land unchanged, the successful-retry test would fail for the correct safety refusal, obscuring the actual state-ordering requirement. The test also matched the earlier pathname remover's exact error string.

The repair writes a structurally complete generation-bound receipt with the current graph format and recovered digest, keeps the forced file-as-directory unlink failure assertion, verifies that failed retirement does not mark the locator collected, that successful retry does mark it collected and removes the target, and that an unrelated sibling sentinel survives. It also requires a sealed-retirement error without depending on obsolete wording. No test skip, timeout increase, CI change, or unrelated source edits.

Validation: source/diff cross-review of #4804's retire_sealed_directory_checked predicate, its capability_dir::remove_receipted_child_directory_nofollow implementation, and the actual test. This runtime has no TraceDecay checkout or Bazel binary, so no Bazel/native macOS pass is claimed and no Cargo was used. Exact-head CI #42502588004 is queued. Verify bazel test //crates/tracedecay-graph-db:unit_test //crates/tracedecay-graph-db:graph_db_suite --test_output=errors with a nonzero regression count and retest after #4804 integration.

This remains a partial Refs #4616 update. Do not close until all five acceptance requirements are proven on master. Mixed draft #4776, Kev/model PRs, release #4763, infra and peer schedules untouched.

devin-ai-integration[bot]

This comment was marked as resolved.

Assert the concrete unavailable error from the sealed-directory unlink
boundary, not any error that happens to mention sealed. Accept the two
currently reviewable collector implementations until the ownership-safe
collector is integrated, without weakening the failure/retry assertions.

Refs #4616

Copy link
Copy Markdown
Owner Author

Refs #4616 — published d89da711 directly to this issue-specific PR, expected-old-head b3a13ddb8cc60ad73b1c4dd9d4145093d42d7468, force=false. GitHub compare verifies a one-file, +7/-2 delta; the remote PR head was independently re-read from REST after publication.

Review fix: the previous error.to_string().contains("sealed") would accept an unrelated sealed-store error as evidence of an actual unlink refusal, making the negative retirement/retry test vacuous. The assertion now requires GraphDbError::Unavailable from the specific sealed-directory retirement failure site. It names the precise message prefixes from the current default-branch remover and the separately reviewable verified-capability collector in #4804, while continuing to require foreign bytes remain intact, collected remains false after refusal, retry reclaims the verified artifact, and an unrelated sibling survives. The related reviewer finding is addressed by source, but its review thread remains for the reviewer to resolve.

Validation: source+exact-commit-diff review; no local repository checkout or Bazel runner, no Cargo. Prior exact-head CI #42502588004 had graph-db unit tests PASSED (the 174-test lane), while broader graph-db suite failed in unrelated staging_footprint::release_sweep_retries_idle_sealed_reader_hibernation timeout and automation/runtime suites failed independently; not a green build. New exact-head CI #42508716334 is pending. Re-run bazel test //crates/tracedecay-graph-db:unit_test //crates/tracedecay-graph-db:graph_db_suite --test_output=errors, confirming nonzero execution, on integrated source before closure.

This fixes test discrimination only, not all sealed-retention acceptance. Keep #4616 open. No changes to mixed draft #4776, model PRs, release #4763, or peer branches.

Copy link
Copy Markdown
Owner Author

Refs #4616 — superseded by clean-history PR #4819, commit c8a1effe. #4819 includes this PR's exact generation_runtime.rs blob plus the four strict collector files from #4804. The joint tree addresses #4804's native graph-db regression failure (old error wording and receipt-free fixture) while retaining this PR's retry-state and sibling-preservation assertions. No original branch was rewritten. Closing as superseded, not merged; the new head still needs independent CI/Bazel proof.

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.

Sealed code-graph generations (tracedecay.sealed/) are never collected; busy project store grew to 56 GB over a 10 GB bound

1 participant