-
Notifications
You must be signed in to change notification settings - Fork 2
fix(maintenance): stop counting unowned attachments as attachment debt #4701
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -23,6 +23,7 @@ | |
| UnrecoverableAttachmentReason, | ||
| plan_orphaned_attachment_relink, | ||
| ) | ||
| from polylogue.storage.blob_liveness import acquired_attachment_missing_ref_predicate | ||
| from polylogue.storage.sqlite.archive_tiers.types import ArchiveTier | ||
| from polylogue.storage.sqlite.migration_runner import ( | ||
| validate_backup_manifest_covers_derived_tier, | ||
|
|
@@ -563,10 +564,9 @@ def closure_counts(source_conn: sqlite3.Connection, index_conn: sqlite3.Connecti | |
| ) | ||
| attachment_missing = int( | ||
| index_conn.execute( | ||
| """ | ||
| f""" | ||
| SELECT COUNT(*) FROM attachments a | ||
| WHERE a.acquisition_status = 'acquired' | ||
| AND NOT EXISTS (SELECT 1 FROM attachment_refs r WHERE r.attachment_id = a.attachment_id) | ||
| WHERE {acquired_attachment_missing_ref_predicate()} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For a writer-generated acquired attachment with an ambiguous owner ( Useful? React with 👍 / 👎. |
||
| """ | ||
| ).fetchone()[0] | ||
| ) | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -38,7 +38,12 @@ | |
| from polylogue.core.raw_coordinates import zip_member_identity_coordinate | ||
| from polylogue.logging import get_logger | ||
| from polylogue.sources.origin_specs import artifact_rule_for_path | ||
| from polylogue.storage.blob_liveness import BlobLivenessProjection, project_index_blob_hashes, project_live_blob_hashes | ||
| from polylogue.storage.blob_liveness import ( | ||
| BlobLivenessProjection, | ||
| acquired_attachment_missing_ref_predicate, | ||
| project_index_blob_hashes, | ||
| project_live_blob_hashes, | ||
| ) | ||
| from polylogue.storage.blob_store import BlobNamespaceEntry, BlobStore | ||
| from polylogue.storage.introspection import column_exists as _column_exists | ||
| from polylogue.storage.introspection import table_exists as _table_exists | ||
|
|
@@ -2332,14 +2337,18 @@ class AttachmentCoverageReport: | |
| # disk somewhere, most of which nothing can ever reach". | ||
| acquired_unreachable_count: int | ||
| acquired_unreachable_sample: tuple[str, ...] | ||
| #: Acquired attachments the writer retained with an ambiguous owner | ||
| #: (ref_count 0, never swept). Unreferenced by construction, so never | ||
| #: coverage debt -- and not reachable either, so they are their own term. | ||
| acquired_unowned_count: int = 0 | ||
|
|
||
| @property | ||
| def ok(self) -> bool: | ||
| return self.acquired_missing_blob_count == 0 | ||
|
|
||
| @property | ||
| def acquired_reachable_count(self) -> int: | ||
| return self.acquired_count - self.acquired_unreachable_count | ||
| return self.acquired_count - self.acquired_unreachable_count - self.acquired_unowned_count | ||
|
|
||
| def to_dict(self) -> dict[str, object]: | ||
| return { | ||
|
|
@@ -2349,6 +2358,7 @@ def to_dict(self) -> dict[str, object]: | |
| "acquired_reachable_count": self.acquired_reachable_count, | ||
| "acquired_unreachable_count": self.acquired_unreachable_count, | ||
| "acquired_unreachable_sample": list(self.acquired_unreachable_sample), | ||
| "acquired_unowned_count": self.acquired_unowned_count, | ||
| "acquired_missing_blob_count": self.acquired_missing_blob_count, | ||
| "acquired_missing_blob_sample": list(self.acquired_missing_blob_sample), | ||
| "unavailable_count": self.unavailable_count, | ||
|
|
@@ -2382,16 +2392,25 @@ def scan_attachment_coverage( | |
| # as its own dimension, distinct from "bytes missing from the blob | ||
| # store" (acquired_missing_blob_count, below). | ||
| unreachable_rows = conn.execute( | ||
| """ | ||
| f""" | ||
| SELECT a.attachment_id AS attachment_id | ||
| FROM attachments a | ||
| WHERE a.acquisition_status = 'acquired' | ||
| AND NOT EXISTS ( | ||
| SELECT 1 FROM attachment_refs r WHERE r.attachment_id = a.attachment_id | ||
| ) | ||
| WHERE {acquired_attachment_missing_ref_predicate()} | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For a single acquired owner-ambiguous attachment with no Useful? React with 👍 / 👎. |
||
| ORDER BY a.attachment_id | ||
| """ | ||
| ).fetchall() | ||
| # The writer's owner-ambiguous retention: unreferenced by construction, | ||
| # so it is reported as its own dimension rather than as debt. | ||
| unowned_count = int( | ||
| conn.execute( | ||
| """ | ||
| SELECT COUNT(*) FROM attachments a | ||
| WHERE a.acquisition_status = 'acquired' | ||
| AND a.ref_count = 0 | ||
| AND NOT EXISTS (SELECT 1 FROM attachment_refs r WHERE r.attachment_id = a.attachment_id) | ||
| """ | ||
| ).fetchone()[0] | ||
| ) | ||
|
|
||
| missing_sample: list[str] = [] | ||
| missing_count = 0 | ||
|
|
@@ -2417,6 +2436,7 @@ def scan_attachment_coverage( | |
| unfetched_count=status_counts.get("unfetched", 0), | ||
| acquired_unreachable_count=len(unreachable_rows), | ||
| acquired_unreachable_sample=unreachable_sample, | ||
| acquired_unowned_count=unowned_count, | ||
| ) | ||
|
|
||
|
|
||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -21,6 +21,23 @@ | |
| from polylogue.storage.introspection import table_exists as _table_exists | ||
|
|
||
|
|
||
| #: An attachment the writer retained with an ambiguous owner is unreferenced by | ||
| #: construction: ``_write_attachments`` inserts it with ``ref_count`` 0 and | ||
| #: deliberately keeps it out of the ref-count sweep, so it never had a ref to | ||
| #: lose. Reference-closure debt and unreachable-coverage debt both mean "refs | ||
| #: went away without the sweep running", which only a non-zero ``ref_count`` | ||
| #: witnesses. Every site that counts ref-less acquired attachments as debt uses | ||
| #: this predicate, so the two states cannot be conflated on one route. | ||
| def acquired_attachment_missing_ref_predicate(alias: str = "a", *, refs_table: str = "attachment_refs") -> str: | ||
| """SQL predicate for an acquired attachment whose refs went away.""" | ||
| return ( | ||
| f"{alias}.acquisition_status = 'acquired'\n" | ||
| f" AND {alias}.ref_count != 0\n" | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
For an archive affected by the documented pre-#3514 full-replace bug, the lost AGENTS.md reference: AGENTS.md:L192-L193 Useful? React with 👍 / 👎. |
||
| f" AND NOT EXISTS (SELECT 1 FROM {refs_table} r " | ||
| f"WHERE r.attachment_id = {alias}.attachment_id)" | ||
| ) | ||
|
|
||
|
|
||
| class LivenessState(str, Enum): | ||
| LIVE = "live" | ||
| UNREFERENCED = "unreferenced" | ||
|
|
@@ -492,6 +509,7 @@ def project_index_blob_hashes(index_conn: sqlite3.Connection) -> BlobLivenessPro | |
|
|
||
| __all__ = [ | ||
| "BLOB_OWNERS", | ||
| "acquired_attachment_missing_ref_predicate", | ||
| "BlobLiveness", | ||
| "BlobLivenessProjection", | ||
| "LivenessState", | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When an acquired owner-ambiguous attachment has
ref_count = 0and no reference, this new filter makes the check succeed, but the success branch still says that every acquired attachment has canonical reference closure. That observable is false for the newly exempted row and contradicts the attachment-coverage summary, which explicitly reports retained unowned attachments; include the unowned count or limit the claim to debt-bearing attachments.Useful? React with 👍 / 👎.