)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":15,"context_line":"The audit step will quarantine a hash directory if and only if it is"},{"line_number":16,"context_line":"found in a PPI-ancestor of its expected partition, i.e. the partition"},{"line_number":17,"context_line":"it would have resided in before a series of PPIs. The length of this"},{"line_number":18,"context_line":"series is hard-coded today to 1. Hash directories that are misplaced for"},{"line_number":19,"context_line":"other reasons are logged as warnings and left untouched."},{"line_number":20,"context_line":""},{"line_number":21,"context_line":"Additional changes include:"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":12,"id":"8bfb7538_9e8ba06a","line":18,"updated":"2026-05-06 14:47:24.000000000","message":"since we know we\u0027re currently in the process of doing back-to-back PPI we should make this a config var.\n\nWhat I don\u0027t want is to make the tool essentially useless at it\u0027s one job (quarantine objects that hash to a different part) by being overly strict.\n\nAlternatively I\u0027m sure we could justify making ancestory unbounded, or even some sort of \"quarantine any mis-hash\" mode.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":15,"context_line":"The audit step will quarantine a hash directory if and only if it is"},{"line_number":16,"context_line":"found in a PPI-ancestor of its expected partition, i.e. the partition"},{"line_number":17,"context_line":"it would have resided in before a series of PPIs. The length of this"},{"line_number":18,"context_line":"series is hard-coded today to 1. Hash directories that are misplaced for"},{"line_number":19,"context_line":"other reasons are logged as warnings and left untouched."},{"line_number":20,"context_line":""},{"line_number":21,"context_line":"Additional changes include:"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":12,"id":"ee1b3b8a_d0983434","line":18,"in_reply_to":"8bfb7538_9e8ba06a","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":20,"context_line":""},{"line_number":21,"context_line":"Additional changes include:"},{"line_number":22,"context_line":"- Replace the STEP_RELINK/STEP_CLEANUP constants with an enum."},{"line_number":23,"context_line":"- Ring-state precondition validation for each step."},{"line_number":24,"context_line":"- Only perform the cleanup step on the lower-half of the partition"},{"line_number":25,"context_line":"  space."},{"line_number":26,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":12,"id":"ae122a46_09cc474e","line":23,"updated":"2026-05-06 14:47:24.000000000","message":"I *think* some of this already existed?  the exitcode\u003d2 isn\u0027t new - is this JUST saying \"audit also expects ring in correct state; where correct is: any non-PPI-in-progress state\"","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":20,"context_line":""},{"line_number":21,"context_line":"Additional changes include:"},{"line_number":22,"context_line":"- Replace the STEP_RELINK/STEP_CLEANUP constants with an enum."},{"line_number":23,"context_line":"- Ring-state precondition validation for each step."},{"line_number":24,"context_line":"- Only perform the cleanup step on the lower-half of the partition"},{"line_number":25,"context_line":"  space."},{"line_number":26,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":12,"id":"7497f386_c6050a70","line":23,"in_reply_to":"ae122a46_09cc474e","updated":"2026-05-12 17:09:08.000000000","message":"This doesn\u0027t apply anymore. Reverted.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":22,"context_line":"- Replace the STEP_RELINK/STEP_CLEANUP constants with an enum."},{"line_number":23,"context_line":"- Ring-state precondition validation for each step."},{"line_number":24,"context_line":"- Only perform the cleanup step on the lower-half of the partition"},{"line_number":25,"context_line":"  space."},{"line_number":26,"context_line":""},{"line_number":27,"context_line":"Change-Id: I6d1de8caa1322168e5e26682a586d11dc606e19a"},{"line_number":28,"context_line":"Signed-off-by: Wael Halbawi \u003cwhalbawi@nvidia.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":12,"id":"d3d245f9_452ea3c3","line":25,"updated":"2026-05-06 14:47:24.000000000","message":"... wait this is new?  Or you\u0027re calling it out as \"we used to this based on state file *implying* cleanup; now we do it explicitly/always for cleanup\" ???","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":22,"context_line":"- Replace the STEP_RELINK/STEP_CLEANUP constants with an enum."},{"line_number":23,"context_line":"- Ring-state precondition validation for each step."},{"line_number":24,"context_line":"- Only perform the cleanup step on the lower-half of the partition"},{"line_number":25,"context_line":"  space."},{"line_number":26,"context_line":""},{"line_number":27,"context_line":"Change-Id: I6d1de8caa1322168e5e26682a586d11dc606e19a"},{"line_number":28,"context_line":"Signed-off-by: Wael Halbawi \u003cwhalbawi@nvidia.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":12,"id":"490912e3_eaf4cfef","line":25,"in_reply_to":"d3d245f9_452ea3c3","updated":"2026-05-12 17:09:08.000000000","message":"This doesn\u0027t apply anymore. Reverted.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":8,"context_line":""},{"line_number":9,"context_line":"The audit step quarantines hashes that were not removed during the"},{"line_number":10,"context_line":"cleanup phase of a part power increase (PPI) operation. In particular,"},{"line_number":11,"context_line":"it allows an operator to declare the PPI operation as \"complete\" so that"},{"line_number":12,"context_line":"the replicator/reconstructor services can be re-enabled. A pre-condition"},{"line_number":13,"context_line":"for the audit step is that no PPI operation is in progress."},{"line_number":14,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":19,"id":"a6437ac7_5ad16d30","line":11,"updated":"2026-08-21 05:26:05.000000000","message":"I think this overstates the expected use-case; it\u0027s not *desirable* to `declare the PPI operation as \"complete\"` before cleanup is finished all parts - we just \"did it\" and need new code to deal with our impatience.\n\nAudit doesn\u0027t make PPIs any less bad operationally; but it will let us cleanup some orphaned fragments introduced from an early PPI exit in a controlled fashion!\n\nA new \"cleanup aware reconstructor\" than can revert handoffs which have been cleaned would allow us to theoretically be more patient in the future - but it wouldn\u0027t help us with our existing problem.  A theoretical `cleanup --force` mode that can run after finish_part_power_increase would be more useful/safe immediately after PPI \"complete\" than audit - but it wouldn\u0027t help us with our existing problem.\n\nThis could theoretically help someone else as written if they\u0027re in our exact same problem - but we shouldn\u0027t encourage people to use/rely on this unless they discover it necessary.\n\nI would prefer:\n\n```\nThe audit step quarantines hashes that were not removed during the cleanup\nphase of a part power increase (PPI) operation because of operational error\nthat prematurely marked the PPI operation as \"complete\".  A pre-condition for\nthe audit step is that no PPI operation is in progress.  You should stop your\nreplicators while you audit a replicated policy.\n```","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":8,"context_line":""},{"line_number":9,"context_line":"The audit step quarantines hashes that were not removed during the"},{"line_number":10,"context_line":"cleanup phase of a part power increase (PPI) operation. In particular,"},{"line_number":11,"context_line":"it allows an operator to declare the PPI operation as \"complete\" so that"},{"line_number":12,"context_line":"the replicator/reconstructor services can be re-enabled. A pre-condition"},{"line_number":13,"context_line":"for the audit step is that no PPI operation is in progress."},{"line_number":14,"context_line":""}],"source_content_type":"text/x-gerrit-commit-message","patch_set":19,"id":"cbd821d0_3d13fc0c","line":11,"in_reply_to":"a6437ac7_5ad16d30","updated":"2026-09-17 04:22:20.000000000","message":"Addressed by squashing in [sq? relinker: Add audit step to quarantine stale hash directories](https://review.opendev.org/c/openstack/swift/+/1005834).","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"}],"/PATCHSET_LEVEL":[{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"07bf5d189ff8d082a9facec1251906036008aedb","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":5,"id":"424ecf3d_e19495e6","updated":"2026-02-19 05:46:40.000000000","message":"Rebased on top of `2c980ac94`","commit_id":"7d9ce3104aa7db412914bd52db0f9430d6ca6ca9"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":7,"id":"2aee4f18_1a3ff69a","updated":"2026-04-09 19:55:24.000000000","message":"this is looking great!  nice find on that hashes list shadow mutation (!!)","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"b1ee163239f5102dde95ca11c2af414c78db4864","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":10,"id":"ae25f184_22656908","updated":"2026-04-16 16:34:09.000000000","message":"recheck","commit_id":"7b6770a647db6cf83f699f377dd01325e18d9b20"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":12,"id":"a0666a89_a338dc1d","updated":"2026-05-06 14:47:24.000000000","message":"1) go ahead and do the plumbing for `max_num_ppi` - probably `max_audit_history_quarantine_threshold\u003d2` (or 3?)  We\u0027ve not had this audit feature for a long time; who knows how many PPIs have gone by.  A conservative default of `\u003d1` is justifiable as long as we can configure it to `\u003d2` in PDX.\n2) I don\u0027t like the new RuntimeError for stale cleanup state in the part filter; I think we need to just consider the \"cleanup prev_part_power filter is wrong\" as a pre-existing bug and either a) maintain it as-is or b) fix it in a pre-factor\n3) I don\u0027t think we need a \"permissive audit all parts for mis-homed foreign hashes\" mode, that\u0027s starting to tread into object-auditor territory and the idea with re-using the relinker for audit was that we could make it more efficient and get it delivered sooner.  Let\u0027s reject it as scope creep and try and get the \"lower half audit for ppi orphans\" merged.\n4) I think that \"quarantined mis-homed ancestor\" path is *necessary* (if you relink and cleanup an ancestor older than reclaim you risk missing a tombstone in the real path) - so since we HAVE to write that path we should *deliver* it BEFORE we scope creep into \"yeah but if it WAS new enough we COULD relink \u0026 cleanup\" - YAGNI.\n\nI struggled to get used to reading:\n\n```\n_is_ppi_ancestor(n, 2n[+1])\n```\n\n... but at this point I think I\u0027ve got it and would advocate *against* changing the interface or name.  It is a useful abstraction worth understanding (if possible):\n\n```\n    def test_is_ppi_ancestor_exahustive(self):\n        def make_part_map(pp):\n            return {n: (2 * n, 2 * n + 1) for n in range(2 ** pp)}\n\n        # sanity\n        prev_pp_3 \u003d make_part_map(3)\n        self.assertEqual({\n            0: (0, 1),  # old n \u003d\u003e 2n, 2n+1\n            1: (2, 3),\n            2: (4, 5),\n            3: (6, 7),\n            4: (8, 9),\n            5: (10, 11),\n            6: (12, 13),\n            7: (14, 15),\n        }, prev_pp_3)\n\n        # if we have parts 0-15 (i.e. pp\u003d4) and we \"found\" a hash sitting in a\n        # part e.g. 2 but our hash shift says THAT hash belongs in 4 (or 5!) we\n        # say can still say \"yes, 2 is an ancestor of 4\"\n        for found, new_parts in prev_pp_3.items():\n            for p in new_parts:\n                self.assertTrue(relinker._is_ppi_ancestor(found, p))\n\n        # similarly for parts 0-31 (i.e. pp\u003d5) we mind find a hash in an\n        # ancestry part (0-16)\n        prev_pp_4 \u003d make_part_map(4)\n        for found, new_parts in prev_pp_4.items():\n            for p in new_parts:\n                self.assertTrue(relinker._is_ppi_ancestor(found, p))\n        # but notably, for any part 0-7, it could be an ancestor of any of\n        # it\u0027s descendants (if you look back 2 generations!)\n        for found, descendents in prev_pp_3.items():\n            for c in descendents:\n                for p in prev_pp_4[c]:\n                    if p \u003d\u003d 0:\n                        # for the hashes expected to be in part 0, the part 0\n                        # is both their parent AND grandparent\n                        self.assertEqual(found, 0)\n                    elif p \u003d\u003d 1:\n                        # for the hashes expected to be in part 1, the part 0\n                        # is both their parent AND grandparent\n                        self.assertEqual(found, 0)\n                    else:\n                        # for any \"normal\" part, including prev_pp_3[1] which\n                        # mapped to (2, 3) - generally it\u0027s grandchildren are\n                        # NOT it\u0027s children\n                        self.assertFalse(\n                            relinker._is_ppi_ancestor(found, p, max_num_ppi\u003d1))\n                    self.assertTrue(\n                        relinker._is_ppi_ancestor(found, p, max_num_ppi\u003d2))\n                    # N.B. default behavior is \"unbounded\"\n                    self.assertTrue(relinker._is_ppi_ancestor(found, p))\n                    self.assertTrue(relinker._is_ppi_ancestor(\n                        found, p, max_num_ppi\u003dNone))\n```","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"ca0bb70d073faab4735a055e5759de98a87d22e1","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":12,"id":"53279fa3_3c6a6900","updated":"2026-05-05 18:00:36.000000000","message":"recheck","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"9d861f2ed5dfa1077db6cc1cff2265b925da4fed","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":12,"id":"8bdd642e_d35db268","updated":"2026-05-01 21:35:17.000000000","message":"recheck","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":13,"id":"bdd48139_73ea5510","updated":"2026-05-12 17:09:08.000000000","message":"@clay.gerrard@gmail.com: Appreciate the thorough review. I\u0027ve addressed the comments related to this change and reverted anything that is not directly related to the `AUDIT` mode.","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"ae302eccc90b75a7ac854ce034dbe009775e924a","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":13,"id":"886cb57d_56747ce4","updated":"2026-05-19 09:44:50.000000000","message":"Tested this on my SAIO. This works as expected, but there is an issue - please see my latest inline comment.","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"be733dcd52ccd95dfc5f0a638795c42efc056fa8","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":13,"id":"ebee6362_b199171d","updated":"2026-05-20 01:01:32.000000000","message":"Thanks @cschwede@nvidia.com for the review.","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"359721965b1b9e042b6173754be652c2ae9341af","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":14,"id":"3aa7c3cb_3c4a1673","updated":"2026-05-26 13:30:44.000000000","message":"Solid implementation with comprehensive testing. Just left a few comments. Thank you very much!","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"7c82ad5426c5aa40373ba3c5a7697dfd3d1abe74","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":14,"id":"14279b5f_b51687cf","updated":"2026-05-21 17:23:10.000000000","message":"recheck","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d4595622d317c8bb7f125e87b70d95abb4d386b5","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":16,"id":"24b4c4d8_b8dd3452","updated":"2026-07-30 03:06:03.000000000","message":"Freshened up that patch and stacked on top of [989469](https://review.opendev.org/c/openstack/swift/+/989469) to make use of the new and improved quarantine API. I also introduced a `quarantined` counter as suggested by @cschwede@nvidia.com.","commit_id":"24f4bebe1637a75ea4ca94dcb5ea2748b650025d"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"9657da57dae52d14c0fb019ea7372a8a66e2a5cf","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":17,"id":"5484e6fe_39816858","updated":"2026-07-31 14:01:58.000000000","message":"After thinking about this some more, I wonder whether quarantine is the right operation at all.\n\nFor example, suppose partition power increased from 10 to 11. A hash now maps to partition 11, but a stale copy remains under `objects/5/` on d1 after being linked into `objects/11/`:\n\n1. d1 was a primary for the old partition 5, which is why the directory exists there.\n2. After the PPI, the replicator interprets that directory as belonging to the current partition 5. If d1 is now a handoff for partition 5, it sends the directory to partition 5\u0027s current primaries and then removes it locally.\n3. If the object was already linked into `objects/11/`, the local removal only drops the stale hardlink, so GETs still work.\n4. The receiving nodes now store a real copy under the wrong partition, and normal replication has no reason to remove it.\n\nIf the object was never linked forward, it is worse: removing the local handoff takes the only serving copy away, while the remaining copy is stored under partition 5 on nodes that will never be queried for partition 11.\n\nCould the audit repair these locations instead? For each mismatched hash, calculate:\n\n```python\nnew_hash_path \u003d replace_partition_in_path(\n    devices, hash_path, self.part_power)\n```\n\nand run the existing `process_location()` reconciliation with cleanup semantics. That links any required files into the partition for the current power and removes the stale source only after the required destination links have been verified.\n\nOnce the object is under partition 11, normal replication handles its node placement correctly. In the already-relinked case, this just verifies the destination and removes the stale hardlinks, with no quarantine entry for an operator to investigate. It also removes the need to infer safety from PPI ancestry arithmetic.\n\nAnything that cannot be safely reconciled can remain in place and fail the pass, as cleanup does today. We can then decide separately whether any of those narrower failure cases warrant quarantine.","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"52a24923ad291f473285732dde734bf86ec8b4ea","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":17,"id":"5e0c2fb5_9fcb9af3","updated":"2026-07-31 16:15:01.000000000","message":"Revoking my -1 after some discussion about this. Maybe Matt has similar concerns to ask for a ```-dry-run``` option?\n\nThe patch itself is fine with me, let\u0027s just make sure we agree that the process/idea to quarantine (instead of relinking) is the safe way to do.","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"e7f7644f6785058caac2e64fc3bbd04f036707e5","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":17,"id":"600a1962_144968bb","in_reply_to":"5484e6fe_39816858","updated":"2026-08-03 00:53:07.000000000","message":"yeah interesting, I think one of the problems is if you run it later and find an object in the wrong parition, but maybe another replica did the right thing and if we throw this in the right partition locally and let the handoff code push is out.. there is a chance that the real object has since been deleted. So the old data rises from the dead and un referenced in container listings.\n\nThe relinker state files also keep around reliner times? maybe \u003c reclaim_age its ok to process_location but post quarantine would be the right answer.\n\nAll I know, is a `--dry-run` can\u0027t hurt, so we can run and see what it actually plans to do first.","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"bec16ca2a486b8633a9dd03b7c2577afe9a123f9","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":18,"id":"cee33958_210be0b3","updated":"2026-08-04 13:33:30.000000000","message":"I might have found another issue.\n\nThe relinker writes \u003cdevice\u003e/relink.objects.json, marking each partition done so an interrupted run can pick up where it left off.\n\nThat file is only ever thrown away in one place: hook_pre_device in relinker.py deletes it if the part_power/next_part_power on disk don\u0027t match the current run.\n\nFor relink and cleanup that happens by itself, because the next PPI step always changes one of those two values.\n\nAudit is different. It runs with (part_power\u003dP, next_part_power\u003dNone), and that pair doesn\u0027t change again until the next PPI. So a finished audit leaves a state file where every partition is already marked done, and partitions_filter hands back an empty list - and the audit step does nothing.","commit_id":"f6cbd18402755ff8c87966cf6011fe26f1df19e3"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"bb797a6fcf602a4f50c9c9c19d4d5bbbc4f093b5","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":18,"id":"91648435_cc84d520","updated":"2026-08-05 15:59:14.000000000","message":"I think the way to move forward with this is to document these two cases:\n\n1. Warning if using replicated policy - stop replicators when running auditor\n2. Manually clear state files if auditor needs to be re-run\n\nThe code itself LGTM, so I think we\u0027re pretty close!","commit_id":"f6cbd18402755ff8c87966cf6011fe26f1df19e3"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"c852038fac23e431090e15a093ea8309261959e7","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":18,"id":"8b5bf4b7_c83ea384","in_reply_to":"cee33958_210be0b3","updated":"2026-08-04 13:34:33.000000000","message":"Which is likely the same Matt thought about?","commit_id":"f6cbd18402755ff8c87966cf6011fe26f1df19e3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":19,"id":"d467169b_0c9dd4d4","updated":"2026-08-21 05:26:05.000000000","message":"I\u0027m pretty sure we should walk back the language in the commit message - IMHO this is a very specific measure of last resort and not a general ergonomic improvement to the PPI process - we\u0027re not trying to \"allow operators\" to do ANYTHING other audit when they\u0027ve ^%\u0026*ed up.\n\nLike we could try to make PPIs better and have commit messages like \"this allows oprators to ...\" - but in reality we\u0027ve prioritized dealing with our current mess FIRST and audit would allow that.  We should carry this change and quarantine some of our trash while we bikeshed on where to implement new config parser helper.\n\nAlternatively if we get the commit message right and the docs consistent we could probably merge it and do the rest in follow-ups.","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"9a985e722c6d58027cd1096d437370249795d016","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":19,"id":"ca2bdf7b_c72a4f9a","updated":"2026-08-20 01:28:57.000000000","message":"recheck\n\nZuul node failure","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":20,"id":"2c255644_ee28c235","updated":"2026-09-17 04:22:20.000000000","message":"Thanks @clay.gerrard@gmail.com for the review. I\u0027ve attempted to address all of your comments. Documentation and commit message changes were pulled in as part of [1005834: sq? relinker: Add audit step to quarantine stale hash directories](https://review.opendev.org/c/openstack/swift/+/1005834).","commit_id":"ec4e5766e5add8402efb603ce87ca84ecdb170b2"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"3652eb946148d24b320a585e9c03b5b3ec96bd2f","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":20,"id":"5b5d586a_3cd29bf6","updated":"2026-09-16 07:52:25.000000000","message":"maybe somethig like this to square up the docs so we can merge this thing?\n\n1005834: sq? relinker: Add audit step to quarantine stale hash directories | https://review.opendev.org/c/openstack/swift/+/1005834","commit_id":"ec4e5766e5add8402efb603ce87ca84ecdb170b2"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"4833098d7424bbec71110bd8ffc9b16e57ba3dae","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":22,"id":"7b1b72e2_f807923b","updated":"2026-09-18 16:43:21.000000000","message":"LGTM, no blocker. Just a short update in the docs, please see inline - could be a follow up as well.","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":22,"id":"613adcc3_838fc959","updated":"2026-09-17 23:40:56.000000000","message":"LOVE IT!\n\nholding +A just in-case Matt or Christian want to disagree with me; but according to the commit message i\u0027m not a co-author so AFAIK i should be able to merge it next week if no one else cares.\n\nFWIW my test fiddling didn\u0027t find much of anything worth squashing; I\u0027d suggest we let it ride and follow-up only if we want to:\n\n1006130: test: Improve relinker audit tests | https://review.opendev.org/c/openstack/swift/+/1006130\n\nNICE WORK!","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"4e2d92be4059dd1041af346c4f76b3b5b4418da7","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":22,"id":"2c152022_690da5df","in_reply_to":"613adcc3_838fc959","updated":"2026-09-18 16:38:13.000000000","message":"I totally dropped the ball on the co-authorship tag. This needs to be fixed!","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"fd7f58cb5721d3b934eebd7007669ac2ed26b8ee","unresolved":false,"context_lines":[],"source_content_type":"","patch_set":23,"id":"1f6f02b0_04557d0f","updated":"2026-09-18 17:03:09.000000000","message":"LGTM.\n\nClay already added his +2 before the commit msg change, and we just discussed my last comments directly - no need to change the docs. Let\u0027s merge it!","commit_id":"7bf3f3d19905e1be2cb1c329c36c58408fc98a3e"}],"doc/manpages/swift-object-relinker.1":[{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"4833098d7424bbec71110bd8ffc9b16e57ba3dae","unresolved":true,"context_lines":[{"line_number":54,"context_line":"completes, and then restart it. Otherwise, primary peers may restore a stale"},{"line_number":55,"context_line":"hash directory after audit quarantines it, preventing audit from making"},{"line_number":56,"context_line":"lasting progress. With an erasure-coded policy, lone stale fragments instead"},{"line_number":57,"context_line":"produce reconstructor errors when peers cannot provide enough fragments to"},{"line_number":58,"context_line":"rebuild them. Those failures prevent the stale fragments from becoming fully"},{"line_number":59,"context_line":"rebuilt, durable dark data."},{"line_number":60,"context_line":""}],"source_content_type":"text/troff","patch_set":22,"id":"a4e3efef_95bd9f7f","line":57,"updated":"2026-09-18 16:43:21.000000000","message":"Same reason as in doc/source/ring_partpower.rst - suggestion from the LLM:\n\n```\n  -lasting progress. With an erasure-coded policy, lone stale fragments instead\n  -produce reconstructor errors when peers cannot provide enough fragments to\n  +lasting progress. For an erasure-coded policy, stop\n  +\\fBobject\\-reconstructor\\fR on all nodes until audit completes everywhere:\n  +handoff reverts can restore stale fragments without reconstruction.\n  +Lone stale fragments can also produce errors when peers lack fragments to\n\n```","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"9176e87bf55969fc685b55c86cd6b30196328221","unresolved":true,"context_lines":[{"line_number":54,"context_line":"completes, and then restart it. Otherwise, primary peers may restore a stale"},{"line_number":55,"context_line":"hash directory after audit quarantines it, preventing audit from making"},{"line_number":56,"context_line":"lasting progress. With an erasure-coded policy, lone stale fragments instead"},{"line_number":57,"context_line":"produce reconstructor errors when peers cannot provide enough fragments to"},{"line_number":58,"context_line":"rebuild them. Those failures prevent the stale fragments from becoming fully"},{"line_number":59,"context_line":"rebuilt, durable dark data."},{"line_number":60,"context_line":""}],"source_content_type":"text/troff","patch_set":22,"id":"92f41bd7_74ce7694","line":57,"in_reply_to":"a4e3efef_95bd9f7f","updated":"2026-09-18 17:01:26.000000000","message":"Discussed this further over the phone. The conclusion is to not stop the object-reconstructor to handle an edge-case where a revert handoff lands a lone fragment in partition P after P has been audited. Operationally, this means that we might have to run audit multiple times and the signal for that is reconstructor errors.","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"}],"doc/source/ring_partpower.rst":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":158,"context_line":"4. Audit old partition locations"},{"line_number":159,"context_line":"--------------------------------"},{"line_number":160,"context_line":""},{"line_number":161,"context_line":"Once the finished ring has been copied to all nodes and re-read, run the audit"},{"line_number":162,"context_line":"on every object storage node, specifying the policy whose partition power was"},{"line_number":163,"context_line":"increased::"},{"line_number":164,"context_line":""}],"source_content_type":"text/x-rst","patch_set":19,"id":"4a858a64_a1d4539c","line":161,"updated":"2026-08-21 05:26:05.000000000","message":"This sort of reads like it\u0027s SOP to run audit immediately after cleanup - if that\u0027s going to to be what people do (because the docs encourage it) I think it might have been reasonable to expect that when audit encouters a mis-placed hash it would just be moved into the correct part as a handoff (basically `swift-object-relinker cleanup --trust-me-bro-ignore-next-part-power`)\n\nBut that\u0027s not what we needed and not what this change delivers.\n\nFor our need the ONLY reasonble thing to do is quarantine, because we\u0027re dealing with data that\u0027s been stale longer than a reclaim age - by a lot - we literally CAN\u0027T rewind the clock and just relink it.\n\nidk, maybe we could for EC - it sort of depends on how the reconstructor\u0027s quarantine threshold configuration is tuned - but it\u0027d be essentially moving an orphan segment from one part into another and loosing all the context as how it got orphaned.  With replicated policies we definately can\u0027t move it - that would just create dark data.\n\nI think the fix here is docs:\n\n\u003e If you suspect you have orphaned segments and dark data from a previous PPI stuck in the consistency engine you MAY run the audit ...\n\nand leave it that?  The obvious question is \"how would I KNOW if I had orphaned segments or dark data from a previous PPI\" ... one way tht this change enables that\u0027s never existed before would be \"point audit at the specific device/part and see what get\u0027s quarantined!\" ... but we could try to document some example reconstructor log lines, or how to find unfinished parts in relinker cleanup status files - I think these could be follow-on changes.  At least I think that would satisfy the spirit of the REVIEW_GUIDLINES:\n\n```\nIf a change does not have adequate documentation it may not be suitable to\nmerge. If a change includes incorrect or misleading documentation or is\ncontrary to *existing* documentation is probably is not suitable to merge.\n\nEvery change could have better documentation.\n```","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":158,"context_line":"4. Audit old partition locations"},{"line_number":159,"context_line":"--------------------------------"},{"line_number":160,"context_line":""},{"line_number":161,"context_line":"Once the finished ring has been copied to all nodes and re-read, run the audit"},{"line_number":162,"context_line":"on every object storage node, specifying the policy whose partition power was"},{"line_number":163,"context_line":"increased::"},{"line_number":164,"context_line":""}],"source_content_type":"text/x-rst","patch_set":19,"id":"37e13faf_27f8905c","line":161,"in_reply_to":"4a858a64_a1d4539c","updated":"2026-09-17 04:22:20.000000000","message":"Addressed by squashing in [sq? relinker: Add audit step to quarantine stale hash directories](https://review.opendev.org/c/openstack/swift/+/1005834).","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":168,"context_line":"in other words, the ring\u0027s ``next_part_power`` must be ``None``. It scans the"},{"line_number":169,"context_line":"old, lower half of the partition namespace and calculates the expected current"},{"line_number":170,"context_line":"partition for each hash directory. A hash directory found in an ancestor of"},{"line_number":171,"context_line":"its expected partition is quarantined. A misplaced hash directory that is not"},{"line_number":172,"context_line":"such an ancestor is left in place and logged as a warning. By default, the"},{"line_number":173,"context_line":"audit considers ancestors from the two most recent partition power increases."},{"line_number":174,"context_line":"This limit may be changed with ``--max-audit-history-quarantine-threshold``"}],"source_content_type":"text/x-rst","patch_set":19,"id":"82ad193f_afb39aa0","line":171,"updated":"2026-08-21 05:26:05.000000000","message":"ok, well at least we\u0027re clear about what audit DOES\n\nit is NOT \"keep doing more cleanup after I\u0027ve already turned the consistency egnine back on\" - it\u0027s very much: \"I trust the consisteny engine from here, throw any garbage in the trash\"","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":168,"context_line":"in other words, the ring\u0027s ``next_part_power`` must be ``None``. It scans the"},{"line_number":169,"context_line":"old, lower half of the partition namespace and calculates the expected current"},{"line_number":170,"context_line":"partition for each hash directory. A hash directory found in an ancestor of"},{"line_number":171,"context_line":"its expected partition is quarantined. A misplaced hash directory that is not"},{"line_number":172,"context_line":"such an ancestor is left in place and logged as a warning. By default, the"},{"line_number":173,"context_line":"audit considers ancestors from the two most recent partition power increases."},{"line_number":174,"context_line":"This limit may be changed with ``--max-audit-history-quarantine-threshold``"}],"source_content_type":"text/x-rst","patch_set":19,"id":"61a1d748_09cb25f2","line":171,"in_reply_to":"82ad193f_afb39aa0","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":188,"context_line":"are already marked complete. Unlike relink and cleanup, a completed audit does"},{"line_number":189,"context_line":"not itself cause another ring-state change, so merely running the command again"},{"line_number":190,"context_line":"does not rescan those partitions."},{"line_number":191,"context_line":""},{"line_number":192,"context_line":"To perform a full rescan, make sure that no relinker process is running, remove"},{"line_number":193,"context_line":"the relevant state file from every device, and then run the audit again::"},{"line_number":194,"context_line":""}],"source_content_type":"text/x-rst","patch_set":19,"id":"77f4eef7_c1930732","line":191,"updated":"2026-08-21 05:26:05.000000000","message":"IMHO, this paragraph is really fantastic!","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":188,"context_line":"are already marked complete. Unlike relink and cleanup, a completed audit does"},{"line_number":189,"context_line":"not itself cause another ring-state change, so merely running the command again"},{"line_number":190,"context_line":"does not rescan those partitions."},{"line_number":191,"context_line":""},{"line_number":192,"context_line":"To perform a full rescan, make sure that no relinker process is running, remove"},{"line_number":193,"context_line":"the relevant state file from every device, and then run the audit again::"},{"line_number":194,"context_line":""}],"source_content_type":"text/x-rst","patch_set":19,"id":"5b565975_b5f8bc79","line":191,"in_reply_to":"77f4eef7_c1930732","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"4833098d7424bbec71110bd8ffc9b16e57ba3dae","unresolved":true,"context_lines":[{"line_number":194,"context_line":"    making lasting progress."},{"line_number":195,"context_line":""},{"line_number":196,"context_line":"    This peer restoration does not occur for an erasure-coded policy. A lone"},{"line_number":197,"context_line":"    stale fragment instead produces reconstructor errors when peers cannot"},{"line_number":198,"context_line":"    provide enough fragments to rebuild it. Those failures prevent the stale"},{"line_number":199,"context_line":"    fragment from becoming fully rebuilt, durable dark data. For example::"},{"line_number":200,"context_line":""}],"source_content_type":"text/x-rst","patch_set":22,"id":"0f15d2fe_7d6ef67f","line":197,"updated":"2026-09-18 16:43:21.000000000","message":"My LLM suggested this change - because a running reconstructor can restore stale hashes into already-audited partitions that subsequent audits skip:\n\n```\n  -    This peer restoration does not occur for an erasure-coded policy. A lone\n  -    stale fragment instead produces reconstructor errors when peers cannot\n  +    For an erasure-coded policy, stop ``object-reconstructor`` on all nodes\n  +    until audit completes everywhere: handoff reverts can restore stale\n  +    fragments without reconstruction. A lone stale fragment can also produce\n  +    reconstructor errors when peers cannot\n```","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":222,"context_line":"the relevant state file from every device, and then run the audit again::"},{"line_number":223,"context_line":""},{"line_number":224,"context_line":"    rm /srv/node/sda/relink.objects-1.json"},{"line_number":225,"context_line":"    swift-object-relinker audit --policy 1"},{"line_number":226,"context_line":""},{"line_number":227,"context_line":"----------"},{"line_number":228,"context_line":"Background"}],"source_content_type":"text/x-rst","patch_set":22,"id":"09fb8f1d_8eb58639","line":225,"updated":"2026-09-17 23:40:56.000000000","message":"IMHO this is top-tier docs - exactly the sort of thing swift should aim for as an operator friendly project - KUDOS.","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"}],"swift/cli/relinker.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":257,"context_line":"            prev_part_power \u003d self.part_power - 1"},{"line_number":258,"context_line":"            if \"prev_part_power\" in self.states:"},{"line_number":259,"context_line":"                # this is a thing that CLEANUP tracks via state file"},{"line_number":260,"context_line":"                assert self.states[\"prev_part_power\"] \u003d\u003d prev_part_power"},{"line_number":261,"context_line":"            # All partitions in the upper half are new partitions and"},{"line_number":262,"context_line":"            # there is nothing to clean up (or audit) there"},{"line_number":263,"context_line":"            cutoff \u003d 2 ** prev_part_power"}],"source_content_type":"text/x-python","patch_set":2,"id":"1d1b7fcd_b101aaf7","line":260,"updated":"2026-04-09 19:55:24.000000000","message":"this was more of a sanity check - but bandit doesn\u0027t like the assert statement\n\nmaybe better to raise a RunTimeError - or even suggest deletion of the state file?   It might depend on how seriously we want to support `prev_part_power \u003c part_power - 1`","commit_id":"714d59901c1d23841ffba1e936ca63779904bb15"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f3869afb8cc00d7533825de05b3448e81ced7d64","unresolved":false,"context_lines":[{"line_number":257,"context_line":"            prev_part_power \u003d self.part_power - 1"},{"line_number":258,"context_line":"            if \"prev_part_power\" in self.states:"},{"line_number":259,"context_line":"                # this is a thing that CLEANUP tracks via state file"},{"line_number":260,"context_line":"                assert self.states[\"prev_part_power\"] \u003d\u003d prev_part_power"},{"line_number":261,"context_line":"            # All partitions in the upper half are new partitions and"},{"line_number":262,"context_line":"            # there is nothing to clean up (or audit) there"},{"line_number":263,"context_line":"            cutoff \u003d 2 ** prev_part_power"}],"source_content_type":"text/x-python","patch_set":2,"id":"30c3ec5f_bd4ade84","line":260,"in_reply_to":"1d1b7fcd_b101aaf7","updated":"2026-04-15 20:25:10.000000000","message":"Acknowledged","commit_id":"714d59901c1d23841ffba1e936ca63779904bb15"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":392,"context_line":"            fname \u003d os.path.join(suff_path, hsh)"},{"line_number":393,"context_line":"            if fname \u003d\u003d replace_partition_in_path("},{"line_number":394,"context_line":"                    self.conf[\u0027devices\u0027], fname, self.next_part_power):"},{"line_number":395,"context_line":"                hashes.remove(hsh)"},{"line_number":396,"context_line":"        return hashes"},{"line_number":397,"context_line":""},{"line_number":398,"context_line":"    def do_relink(self, device, hash_path, new_hash_path, filename,"}],"source_content_type":"text/x-python","patch_set":7,"id":"605091d4_3d0bfed9","side":"PARENT","line":395,"updated":"2026-04-09 19:55:24.000000000","message":"holy crap, we create a copy with `hashes \u003d list(hashes)` but that\u0027s just shadowing - this should be blowing up in prod - WTF is going on!?\n\nI think this is probably very very bad news:\n\n```\n\u003e\u003e\u003e a \u003d list(range(5))\n\u003e\u003e\u003e for x in a:\n...     if x \u003d\u003d 2:\n...         a.remove(x)\n...     print(x, a)\n... \n0 [0, 1, 2, 3, 4]\n1 [0, 1, 2, 3, 4]\n2 [0, 1, 3, 4]\n4 [0, 1, 3, 4]\n```","commit_id":"9d684080daf73f2506655bf1a54c96b8685d7f93"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f3869afb8cc00d7533825de05b3448e81ced7d64","unresolved":false,"context_lines":[{"line_number":392,"context_line":"            fname \u003d os.path.join(suff_path, hsh)"},{"line_number":393,"context_line":"            if fname \u003d\u003d replace_partition_in_path("},{"line_number":394,"context_line":"                    self.conf[\u0027devices\u0027], fname, self.next_part_power):"},{"line_number":395,"context_line":"                hashes.remove(hsh)"},{"line_number":396,"context_line":"        return hashes"},{"line_number":397,"context_line":""},{"line_number":398,"context_line":"    def do_relink(self, device, hash_path, new_hash_path, filename,"}],"source_content_type":"text/x-python","patch_set":7,"id":"c2a3d6f9_63ad00e7","side":"PARENT","line":395,"in_reply_to":"605091d4_3d0bfed9","updated":"2026-04-15 20:25:10.000000000","message":"Acknowledged","commit_id":"9d684080daf73f2506655bf1a54c96b8685d7f93"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":659,"context_line":"            new_hash_path \u003d replace_partition_in_path("},{"line_number":660,"context_line":"                self.conf[\u0027devices\u0027], hash_path, self.next_part_power)"},{"line_number":661,"context_line":"            if new_hash_path \u003d\u003d hash_path:"},{"line_number":662,"context_line":"                continue"},{"line_number":663,"context_line":"            self.process_location(device, hash_path, new_hash_path)"},{"line_number":664,"context_line":""},{"line_number":665,"context_line":"        # any unmounted devices don\u0027t trigger the pre_device trigger."}],"source_content_type":"text/x-python","patch_set":7,"id":"45d2596b_9b572619","side":"PARENT","line":662,"updated":"2026-04-09 19:55:24.000000000","message":"I find I\u0027m still confused about the *apparent* duplication this logic and `self.hashes_filter` - i can\u0027t tell if BOTH are needed for correctness or if EITHER would be fine?  Might be worth getting the bottom of - probably better as a pre-factor as opposed to \"cargo cult complexity\" that we \"try to ignore while review/understanding\" the correctness and completeness of the new tri-state audit hash engine.","commit_id":"9d684080daf73f2506655bf1a54c96b8685d7f93"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f3869afb8cc00d7533825de05b3448e81ced7d64","unresolved":false,"context_lines":[{"line_number":659,"context_line":"            new_hash_path \u003d replace_partition_in_path("},{"line_number":660,"context_line":"                self.conf[\u0027devices\u0027], hash_path, self.next_part_power)"},{"line_number":661,"context_line":"            if new_hash_path \u003d\u003d hash_path:"},{"line_number":662,"context_line":"                continue"},{"line_number":663,"context_line":"            self.process_location(device, hash_path, new_hash_path)"},{"line_number":664,"context_line":""},{"line_number":665,"context_line":"        # any unmounted devices don\u0027t trigger the pre_device trigger."}],"source_content_type":"text/x-python","patch_set":7,"id":"4800db97_9ed598e6","side":"PARENT","line":662,"in_reply_to":"45d2596b_9b572619","updated":"2026-04-15 20:25:10.000000000","message":"I think it\u0027s unintended duplication where the second one protected us from \"blowing up in prod\". First came the inline filter: [779566](https://review.opendev.org/c/openstack/swift/+/779566/8/swift/cli/relinker.py#396), then the buggy hook: [695344](https://review.opendev.org/c/openstack/swift/+/695344/9/swift/cli/relinker.py#137)","commit_id":"9d684080daf73f2506655bf1a54c96b8685d7f93"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":757,"context_line":"    \"\"\""},{"line_number":758,"context_line":"    Fork Relinker workers based on config and wait for them to finish."},{"line_number":759,"context_line":""},{"line_number":760,"context_line":"    :param do_cleanup: boolean, if workers should perform cleanup step"},{"line_number":761,"context_line":"    :param conf: dict, config options"},{"line_number":762,"context_line":"    :param logger: SwiftLogAdapter instance"},{"line_number":763,"context_line":"    :kwarg device_list: list of strings, optionally limit to specific devices"}],"source_content_type":"text/x-python","patch_set":7,"id":"33633f06_b63d6fa1","side":"PARENT","line":760,"updated":"2026-04-09 19:55:24.000000000","message":"nice cleanup on this doc-string - the input param was already `step` from \n\n\u003e     return parallel_process(args.action, conf, logger, args.device_list)","commit_id":"9d684080daf73f2506655bf1a54c96b8685d7f93"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f3869afb8cc00d7533825de05b3448e81ced7d64","unresolved":false,"context_lines":[{"line_number":757,"context_line":"    \"\"\""},{"line_number":758,"context_line":"    Fork Relinker workers based on config and wait for them to finish."},{"line_number":759,"context_line":""},{"line_number":760,"context_line":"    :param do_cleanup: boolean, if workers should perform cleanup step"},{"line_number":761,"context_line":"    :param conf: dict, config options"},{"line_number":762,"context_line":"    :param logger: SwiftLogAdapter instance"},{"line_number":763,"context_line":"    :kwarg device_list: list of strings, optionally limit to specific devices"}],"source_content_type":"text/x-python","patch_set":7,"id":"dffe2b5a_2019ed07","side":"PARENT","line":760,"in_reply_to":"33633f06_b63d6fa1","updated":"2026-04-15 20:25:10.000000000","message":"Acknowledged","commit_id":"9d684080daf73f2506655bf1a54c96b8685d7f93"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":424,"context_line":"        else:"},{"line_number":425,"context_line":"            expected_part_power \u003d self.next_part_power"},{"line_number":426,"context_line":""},{"line_number":427,"context_line":"        mishashed \u003d list(hashes)"},{"line_number":428,"context_line":"        for hsh in hashes:"},{"line_number":429,"context_line":"            fname \u003d os.path.join(suff_path, hsh)"},{"line_number":430,"context_line":"            if fname \u003d\u003d replace_partition_in_path("}],"source_content_type":"text/x-python","patch_set":7,"id":"01580258_ed9a5ad3","line":427,"updated":"2026-04-09 19:55:24.000000000","message":"starting with an empty list also avoids the need to make a copy that we can mutate while iterating","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f3869afb8cc00d7533825de05b3448e81ced7d64","unresolved":false,"context_lines":[{"line_number":424,"context_line":"        else:"},{"line_number":425,"context_line":"            expected_part_power \u003d self.next_part_power"},{"line_number":426,"context_line":""},{"line_number":427,"context_line":"        mishashed \u003d list(hashes)"},{"line_number":428,"context_line":"        for hsh in hashes:"},{"line_number":429,"context_line":"            fname \u003d os.path.join(suff_path, hsh)"},{"line_number":430,"context_line":"            if fname \u003d\u003d replace_partition_in_path("}],"source_content_type":"text/x-python","patch_set":7,"id":"b6b8d937_80ab90a7","line":427,"in_reply_to":"01580258_ed9a5ad3","updated":"2026-04-15 20:25:10.000000000","message":"Acknowledged","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":429,"context_line":"            fname \u003d os.path.join(suff_path, hsh)"},{"line_number":430,"context_line":"            if fname \u003d\u003d replace_partition_in_path("},{"line_number":431,"context_line":"                    self.conf[\u0027devices\u0027], fname, expected_part_power):"},{"line_number":432,"context_line":"                mishashed.remove(hsh)"},{"line_number":433,"context_line":"        return mishashed"},{"line_number":434,"context_line":""},{"line_number":435,"context_line":"    def do_relink(self, device, hash_path, new_hash_path, filename,"}],"source_content_type":"text/x-python","patch_set":7,"id":"0d9561e4_139ea763","line":432,"updated":"2026-04-09 19:55:24.000000000","message":"I like `return mishashed` A LOT - having the semantic meaning of the returned list of strings encoded into the function\u0027s named return value is good intuition - KUDOS\n\nnit: I probably would have found this more *obvious* if `mishased \u003d []` started life empty and got \"filled\" with `fname !\u003d epected_path`","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f3869afb8cc00d7533825de05b3448e81ced7d64","unresolved":false,"context_lines":[{"line_number":429,"context_line":"            fname \u003d os.path.join(suff_path, hsh)"},{"line_number":430,"context_line":"            if fname \u003d\u003d replace_partition_in_path("},{"line_number":431,"context_line":"                    self.conf[\u0027devices\u0027], fname, expected_part_power):"},{"line_number":432,"context_line":"                mishashed.remove(hsh)"},{"line_number":433,"context_line":"        return mishashed"},{"line_number":434,"context_line":""},{"line_number":435,"context_line":"    def do_relink(self, device, hash_path, new_hash_path, filename,"}],"source_content_type":"text/x-python","patch_set":7,"id":"be8d9a84_e795fe0a","line":432,"in_reply_to":"0d9561e4_139ea763","updated":"2026-04-15 20:25:10.000000000","message":"Goes away entirely in [984664](https://review.opendev.org/c/openstack/swift/+/984664)","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":741,"context_line":"                new_hash_path \u003d replace_partition_in_path("},{"line_number":742,"context_line":"                    self.conf[\u0027devices\u0027], hash_path, self.next_part_power)"},{"line_number":743,"context_line":"                if new_hash_path \u003d\u003d hash_path:"},{"line_number":744,"context_line":"                    continue"},{"line_number":745,"context_line":"                self.process_location(device, hash_path, new_hash_path)"},{"line_number":746,"context_line":""},{"line_number":747,"context_line":"        # any unmounted devices don\u0027t trigger the pre_device trigger."}],"source_content_type":"text/x-python","patch_set":7,"id":"92c76b0d_3721911d","line":744,"updated":"2026-04-09 19:55:24.000000000","message":"one reason I think this distraction about `hashes_filter` \"jumps off the diff\" - is because we\u0027re not applying the \"double check new_hash_path\" logic for `step \u003d\u003d AUDIT` (why would we assume that is correct, if we trust the old code enough to maintain it\u0027s current behavior)","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f3869afb8cc00d7533825de05b3448e81ced7d64","unresolved":false,"context_lines":[{"line_number":741,"context_line":"                new_hash_path \u003d replace_partition_in_path("},{"line_number":742,"context_line":"                    self.conf[\u0027devices\u0027], hash_path, self.next_part_power)"},{"line_number":743,"context_line":"                if new_hash_path \u003d\u003d hash_path:"},{"line_number":744,"context_line":"                    continue"},{"line_number":745,"context_line":"                self.process_location(device, hash_path, new_hash_path)"},{"line_number":746,"context_line":""},{"line_number":747,"context_line":"        # any unmounted devices don\u0027t trigger the pre_device trigger."}],"source_content_type":"text/x-python","patch_set":7,"id":"ff5a1ca9_d183193e","line":744,"in_reply_to":"92c76b0d_3721911d","updated":"2026-04-15 20:25:10.000000000","message":"Acknowledged","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":777,"context_line":"            if self.step in (Step.RELINK, Step.CLEANUP):"},{"line_number":778,"context_line":"                if not ring.next_part_power:"},{"line_number":779,"context_line":"                    continue"},{"line_number":780,"context_line":"                part_power_increased \u003d ring.next_part_power \u003d\u003d ring.part_power"},{"line_number":781,"context_line":"                do_cleanup \u003d self.step \u003d\u003d Step.CLEANUP"},{"line_number":782,"context_line":"                if do_cleanup !\u003d part_power_increased:"},{"line_number":783,"context_line":"                    continue"}],"source_content_type":"text/x-python","patch_set":7,"id":"bec2feca_22c01487","line":780,"updated":"2026-04-09 19:55:24.000000000","message":"How we think these ring attributes work:\n\n```\ns-r-b prepare_part_power \u003d\u003e ring.next_part_power gets set to your target\n\u003crun relink\u003e\ns-r-b increase_part_power \u003d\u003e ring.part_power gets set to ring.next_part_power\n  (N.B. next_part_power is non-None \u003d\u003d ring.part_power)\n\u003crun cleanup\u003e\ns-r-b finish_part_power \u003d\u003e ring.next_part_power \u003d None\n```","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":777,"context_line":"            if self.step in (Step.RELINK, Step.CLEANUP):"},{"line_number":778,"context_line":"                if not ring.next_part_power:"},{"line_number":779,"context_line":"                    continue"},{"line_number":780,"context_line":"                part_power_increased \u003d ring.next_part_power \u003d\u003d ring.part_power"},{"line_number":781,"context_line":"                do_cleanup \u003d self.step \u003d\u003d Step.CLEANUP"},{"line_number":782,"context_line":"                if do_cleanup !\u003d part_power_increased:"},{"line_number":783,"context_line":"                    continue"}],"source_content_type":"text/x-python","patch_set":7,"id":"467828d1_32ea9c81","line":780,"in_reply_to":"bec2feca_22c01487","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":780,"context_line":"                part_power_increased \u003d ring.next_part_power \u003d\u003d ring.part_power"},{"line_number":781,"context_line":"                do_cleanup \u003d self.step \u003d\u003d Step.CLEANUP"},{"line_number":782,"context_line":"                if do_cleanup !\u003d part_power_increased:"},{"line_number":783,"context_line":"                    continue"},{"line_number":784,"context_line":"            elif self.step \u003d\u003d Step.AUDIT:"},{"line_number":785,"context_line":"                if ring.next_part_power is not None:"},{"line_number":786,"context_line":"                    self.logger.warning("}],"source_content_type":"text/x-python","patch_set":7,"id":"bfadf223_25e8820e","line":783,"updated":"2026-04-09 19:55:24.000000000","message":"perfect, so this is saying that \"you can only cleanup if the ring is in the `increase_part_power` step\"","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f3869afb8cc00d7533825de05b3448e81ced7d64","unresolved":false,"context_lines":[{"line_number":780,"context_line":"                part_power_increased \u003d ring.next_part_power \u003d\u003d ring.part_power"},{"line_number":781,"context_line":"                do_cleanup \u003d self.step \u003d\u003d Step.CLEANUP"},{"line_number":782,"context_line":"                if do_cleanup !\u003d part_power_increased:"},{"line_number":783,"context_line":"                    continue"},{"line_number":784,"context_line":"            elif self.step \u003d\u003d Step.AUDIT:"},{"line_number":785,"context_line":"                if ring.next_part_power is not None:"},{"line_number":786,"context_line":"                    self.logger.warning("}],"source_content_type":"text/x-python","patch_set":7,"id":"e7cdc2bc_f0c5d8e5","line":783,"in_reply_to":"bfadf223_25e8820e","updated":"2026-04-15 20:25:10.000000000","message":"Acknowledged","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"52817ff6fef95d13395b51e76cbc941990e4d65c","unresolved":true,"context_lines":[{"line_number":782,"context_line":"                if do_cleanup !\u003d part_power_increased:"},{"line_number":783,"context_line":"                    continue"},{"line_number":784,"context_line":"            elif self.step \u003d\u003d Step.AUDIT:"},{"line_number":785,"context_line":"                if ring.next_part_power is not None:"},{"line_number":786,"context_line":"                    self.logger.warning("},{"line_number":787,"context_line":"                        f\"policy\u003d{policy.name}: \""},{"line_number":788,"context_line":"                        \"Skipping audit as relink/cleanup is in progress\")"}],"source_content_type":"text/x-python","patch_set":7,"id":"96bd9c3b_76b6fc4c","line":785,"updated":"2026-04-09 19:55:24.000000000","message":"ok, so there is some kind of (non-obvious?) mirroring between \"you can only relink/cleanup when you\u0027re in the right phase of a PPI\" and \"you can only audit when you\u0027re NOT in one of the PPI phases\"","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f3869afb8cc00d7533825de05b3448e81ced7d64","unresolved":false,"context_lines":[{"line_number":782,"context_line":"                if do_cleanup !\u003d part_power_increased:"},{"line_number":783,"context_line":"                    continue"},{"line_number":784,"context_line":"            elif self.step \u003d\u003d Step.AUDIT:"},{"line_number":785,"context_line":"                if ring.next_part_power is not None:"},{"line_number":786,"context_line":"                    self.logger.warning("},{"line_number":787,"context_line":"                        f\"policy\u003d{policy.name}: \""},{"line_number":788,"context_line":"                        \"Skipping audit as relink/cleanup is in progress\")"}],"source_content_type":"text/x-python","patch_set":7,"id":"58a4b4c0_f5374e6e","line":785,"in_reply_to":"96bd9c3b_76b6fc4c","updated":"2026-04-15 20:25:10.000000000","message":"Correct. Worth investigating maintaining explicit state somewhere (ring file?) rather than implicitly deducing from part_power and next_part_power.","commit_id":"806866426dde8c77f72fd85e3ae546f944a6e4e3"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":234,"context_line":"        # Remove all non partitions first (eg: auditor_status_ALL.json)"},{"line_number":235,"context_line":"        partitions \u003d [p for p in partitions if p.isdigit()]"},{"line_number":236,"context_line":""},{"line_number":237,"context_line":"        relinking \u003d (self.part_power !\u003d self.next_part_power)"},{"line_number":238,"context_line":"        if relinking:"},{"line_number":239,"context_line":"            # All partitions in the upper half are new partitions and there is"},{"line_number":240,"context_line":"            # nothing to relink there"}],"source_content_type":"text/x-python","patch_set":12,"id":"be523fae_5ef28a04","side":"PARENT","line":237,"updated":"2026-05-06 14:47:24.000000000","message":"IIUC this is no longer sufficient to exclusively identifiy step \u003d\u003d relink b/c during audit `self.next_part_power \u003d\u003d None !\u003d self.part_power`","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":234,"context_line":"        # Remove all non partitions first (eg: auditor_status_ALL.json)"},{"line_number":235,"context_line":"        partitions \u003d [p for p in partitions if p.isdigit()]"},{"line_number":236,"context_line":""},{"line_number":237,"context_line":"        relinking \u003d (self.part_power !\u003d self.next_part_power)"},{"line_number":238,"context_line":"        if relinking:"},{"line_number":239,"context_line":"            # All partitions in the upper half are new partitions and there is"},{"line_number":240,"context_line":"            # nothing to relink there"}],"source_content_type":"text/x-python","patch_set":12,"id":"e404a74b_8a5d178d","side":"PARENT","line":237,"in_reply_to":"be523fae_5ef28a04","updated":"2026-05-12 17:09:08.000000000","message":"Correct.","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":240,"context_line":"            # nothing to relink there"},{"line_number":241,"context_line":"            partitions \u003d [part for part in partitions"},{"line_number":242,"context_line":"                          if int(part) \u003c 2 ** self.part_power]"},{"line_number":243,"context_line":"        elif \"prev_part_power\" in self.states:"},{"line_number":244,"context_line":"            # All partitions in the upper half are new partitions and there is"},{"line_number":245,"context_line":"            # nothing to clean up there"},{"line_number":246,"context_line":"            partitions \u003d [part for part in partitions"}],"source_content_type":"text/x-python","patch_set":12,"id":"e5291aef_fc31636c","side":"PARENT","line":243,"updated":"2026-05-06 14:47:24.000000000","message":"if you read `hook_pre_device` it pretty clearly reads that if the state-file doesn\u0027t match what we expect (`next_pp \u003d\u003d next_pp`) we unlink it and continue like it doesn\u0027t exist; so `prev_part_power in states` is clearly a kind of \"best effort\"\n\n... the fallback is \"cleanup all parts\" (inefficient, but not wrong)\n\nI can *imagine* a \"permissive mode audit\" that checks ALL parts for mis-homed hashes and quarntines any that don\u0027t belong there.  Similar to configurable max_ppi_quarantine_threshold \u003d 32 we might want to allow for audit to \"visit all parts\" as a *fallback*\n\nThat said, I think it would actually be nice improvement if the \"cleanup only lower half\" was less dependent on transient-state-file.  And also agree that AUDIT could (should?) sensibly (default?) to do \"just the lower half\"","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":false,"context_lines":[{"line_number":240,"context_line":"            # nothing to relink there"},{"line_number":241,"context_line":"            partitions \u003d [part for part in partitions"},{"line_number":242,"context_line":"                          if int(part) \u003c 2 ** self.part_power]"},{"line_number":243,"context_line":"        elif \"prev_part_power\" in self.states:"},{"line_number":244,"context_line":"            # All partitions in the upper half are new partitions and there is"},{"line_number":245,"context_line":"            # nothing to clean up there"},{"line_number":246,"context_line":"            partitions \u003d [part for part in partitions"}],"source_content_type":"text/x-python","patch_set":12,"id":"07b4fe88_f3d43539","side":"PARENT","line":243,"in_reply_to":"491876a4_236e9f05","updated":"2026-08-21 05:26:05.000000000","message":"so we didn\u0027t make cleanup optimization any less dependent on transite state (orthogonal)\n\nnor provide anyway to audit the upper half (un-necessary)","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":240,"context_line":"            # nothing to relink there"},{"line_number":241,"context_line":"            partitions \u003d [part for part in partitions"},{"line_number":242,"context_line":"                          if int(part) \u003c 2 ** self.part_power]"},{"line_number":243,"context_line":"        elif \"prev_part_power\" in self.states:"},{"line_number":244,"context_line":"            # All partitions in the upper half are new partitions and there is"},{"line_number":245,"context_line":"            # nothing to clean up there"},{"line_number":246,"context_line":"            partitions \u003d [part for part in partitions"}],"source_content_type":"text/x-python","patch_set":12,"id":"491876a4_236e9f05","side":"PARENT","line":243,"in_reply_to":"e5291aef_fc31636c","updated":"2026-05-12 17:09:08.000000000","message":"\u003e... the fallback is \"cleanup all parts\" (inefficient, but not wrong)\n\nRepeating the conclusion from an offline discussion: In its current form, the code relies on the state file to determine if cleanup is happening after a relink or a PPI cancellation. If it is after a relink, the code attempts to restrict the partition space to the lower half, otherwise it cleans up everything.\n\n\n\u003eAnd also agree that AUDIT could (should?) sensibly (default?) to do \"just the lower half\"\n\nI agree.","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":244,"context_line":"            # All partitions in the upper half are new partitions and there is"},{"line_number":245,"context_line":"            # nothing to clean up there"},{"line_number":246,"context_line":"            partitions \u003d [part for part in partitions"},{"line_number":247,"context_line":"                          if int(part) \u003c 2 ** self.states[\"prev_part_power\"]]"},{"line_number":248,"context_line":""},{"line_number":249,"context_line":"        # Format: { \u0027part\u0027: processed }"},{"line_number":250,"context_line":"        if self.states[\"state\"]:"}],"source_content_type":"text/x-python","patch_set":12,"id":"80e3a2e1_4dc9d5df","side":"PARENT","line":247,"updated":"2026-05-06 14:47:24.000000000","message":"I think using `self.states[\u0027prev_part_power\u0027]` is significantly worse than `self.part_power - 1`\n\nRunning cleanup with a stale `state_file[\u0027part_power\u0027] \u003d 10` while having `self.part_power \u003d 12` isn\u0027t just \"inefficient\" - it would skip relink/cleanup on parts 1024-2048 (??)\n\nI think this would be \"strictly better\" if `cleanup` just only/always filtered `parts \u003c self.part_power - 1`","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":244,"context_line":"            # All partitions in the upper half are new partitions and there is"},{"line_number":245,"context_line":"            # nothing to clean up there"},{"line_number":246,"context_line":"            partitions \u003d [part for part in partitions"},{"line_number":247,"context_line":"                          if int(part) \u003c 2 ** self.states[\"prev_part_power\"]]"},{"line_number":248,"context_line":""},{"line_number":249,"context_line":"        # Format: { \u0027part\u0027: processed }"},{"line_number":250,"context_line":"        if self.states[\"state\"]:"}],"source_content_type":"text/x-python","patch_set":12,"id":"e15ed781_a69fed46","side":"PARENT","line":247,"in_reply_to":"80e3a2e1_4dc9d5df","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":46,"context_line":"EXIT_SUCCESS \u003d 0"},{"line_number":47,"context_line":"EXIT_NO_APPLICABLE_POLICY \u003d 2"},{"line_number":48,"context_line":"EXIT_ERROR \u003d 1"},{"line_number":49,"context_line":"DEFAULT_STATS_INTERVAL \u003d 300.0"},{"line_number":50,"context_line":""},{"line_number":51,"context_line":""},{"line_number":52,"context_line":"class Step(Enum):"}],"source_content_type":"text/x-python","patch_set":12,"id":"9e5cd0c2_1a68d99c","line":49,"updated":"2026-05-06 14:47:24.000000000","message":"if we\u0027re going to \"hard code\" something - this is where I would expect to see/change it","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":46,"context_line":"EXIT_SUCCESS \u003d 0"},{"line_number":47,"context_line":"EXIT_NO_APPLICABLE_POLICY \u003d 2"},{"line_number":48,"context_line":"EXIT_ERROR \u003d 1"},{"line_number":49,"context_line":"DEFAULT_STATS_INTERVAL \u003d 300.0"},{"line_number":50,"context_line":""},{"line_number":51,"context_line":""},{"line_number":52,"context_line":"class Step(Enum):"}],"source_content_type":"text/x-python","patch_set":12,"id":"9e8a2d93_dfc8104b","line":49,"in_reply_to":"9e5cd0c2_1a68d99c","updated":"2026-05-12 17:09:08.000000000","message":"Done. Thanks!","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":108,"context_line":""},{"line_number":109,"context_line":"def _is_ppi_ancestor(found_part, expected_part, max_num_ppi\u003dNone):"},{"line_number":110,"context_line":"    \"\"\""},{"line_number":111,"context_line":"    Determine if `expected_part` descended from `found_part` via a sequence of"},{"line_number":112,"context_line":"    partition power increase operations. The search is limited to at most"},{"line_number":113,"context_line":"    `max_num_ppi` operations if it is not None, and is unlimited otherwise."},{"line_number":114,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":12,"id":"750ff2d8_2be37c4a","line":111,"updated":"2026-05-06 14:47:24.000000000","message":"I read \"descended\" as `(2n, 2n+1)` like if I found `5` it is an \"descendet\" of... `2` b/c `2(2)+1\u003d5`\n\nor \"2\" is an ancestor of \"5\"\n\nfound ancestor expected \u003d expect descended found","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":108,"context_line":""},{"line_number":109,"context_line":"def _is_ppi_ancestor(found_part, expected_part, max_num_ppi\u003dNone):"},{"line_number":110,"context_line":"    \"\"\""},{"line_number":111,"context_line":"    Determine if `expected_part` descended from `found_part` via a sequence of"},{"line_number":112,"context_line":"    partition power increase operations. The search is limited to at most"},{"line_number":113,"context_line":"    `max_num_ppi` operations if it is not None, and is unlimited otherwise."},{"line_number":114,"context_line":"    \"\"\""}],"source_content_type":"text/x-python","patch_set":12,"id":"dfb00749_d70511bb","line":111,"in_reply_to":"750ff2d8_2be37c4a","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":228,"context_line":"                on_disk_part_power \u003d state_from_disk[\"part_power\"]"},{"line_number":229,"context_line":"                if on_disk_part_power !\u003d self.states[\"part_power\"]:"},{"line_number":230,"context_line":"                    self.states[\"prev_part_power\"] \u003d on_disk_part_power"},{"line_number":231,"context_line":"                    raise ValueError"},{"line_number":232,"context_line":"                self.states[\"state\"].update(state_from_disk[\"state\"])"},{"line_number":233,"context_line":"        except (ValueError, TypeError, KeyError):"},{"line_number":234,"context_line":"            # Bad state file: remove the file to restart from scratch"}],"source_content_type":"text/x-python","patch_set":12,"id":"a9819e8a_4d3894db","line":231,"updated":"2026-05-06 14:47:24.000000000","message":"this is gross - we pickup the state from disk, and then carry it forward.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":228,"context_line":"                on_disk_part_power \u003d state_from_disk[\"part_power\"]"},{"line_number":229,"context_line":"                if on_disk_part_power !\u003d self.states[\"part_power\"]:"},{"line_number":230,"context_line":"                    self.states[\"prev_part_power\"] \u003d on_disk_part_power"},{"line_number":231,"context_line":"                    raise ValueError"},{"line_number":232,"context_line":"                self.states[\"state\"].update(state_from_disk[\"state\"])"},{"line_number":233,"context_line":"        except (ValueError, TypeError, KeyError):"},{"line_number":234,"context_line":"            # Bad state file: remove the file to restart from scratch"}],"source_content_type":"text/x-python","patch_set":12,"id":"28fea78a_7d88acd1","line":231,"in_reply_to":"a9819e8a_4d3894db","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":279,"context_line":"            partitions \u003d [part for part in partitions"},{"line_number":280,"context_line":"                          if int(part) \u003c 2 ** (self.part_power - 1)]"},{"line_number":281,"context_line":"        else:"},{"line_number":282,"context_line":"            raise RuntimeError(f\"Invalid step: {self.step}\")"},{"line_number":283,"context_line":""},{"line_number":284,"context_line":"        # Format: { \u0027part\u0027: processed }"},{"line_number":285,"context_line":"        if self.states[\"state\"]:"}],"source_content_type":"text/x-python","patch_set":12,"id":"dcfa9bb4_cc8623f8","line":282,"updated":"2026-05-06 14:47:24.000000000","message":"oic, all this `RuntimeError` stuff in the partitions filter is *new* - yuk\n\nCan we just not do this yet?","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":279,"context_line":"            partitions \u003d [part for part in partitions"},{"line_number":280,"context_line":"                          if int(part) \u003c 2 ** (self.part_power - 1)]"},{"line_number":281,"context_line":"        else:"},{"line_number":282,"context_line":"            raise RuntimeError(f\"Invalid step: {self.step}\")"},{"line_number":283,"context_line":""},{"line_number":284,"context_line":"        # Format: { \u0027part\u0027: processed }"},{"line_number":285,"context_line":"        if self.states[\"state\"]:"}],"source_content_type":"text/x-python","patch_set":12,"id":"748580ea_623431a8","line":282,"in_reply_to":"dcfa9bb4_cc8623f8","updated":"2026-05-12 17:09:08.000000000","message":"Done","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":425,"context_line":"        if self.step \u003d\u003d Step.AUDIT:"},{"line_number":426,"context_line":"            expected_part_power \u003d self.part_power"},{"line_number":427,"context_line":"        else:"},{"line_number":428,"context_line":"            expected_part_power \u003d self.next_part_power"},{"line_number":429,"context_line":"        mismatched_hashes \u003d list()"},{"line_number":430,"context_line":"        for hsh in hashes:"},{"line_number":431,"context_line":"            fname \u003d os.path.join(suff_path, hsh)"}],"source_content_type":"text/x-python","patch_set":12,"id":"6cea5cea_8e998f41","line":428,"updated":"2026-05-06 14:47:24.000000000","message":"I think I might use this `expect_part_power` pattern in `partitions_filter` as well?","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":425,"context_line":"        if self.step \u003d\u003d Step.AUDIT:"},{"line_number":426,"context_line":"            expected_part_power \u003d self.part_power"},{"line_number":427,"context_line":"        else:"},{"line_number":428,"context_line":"            expected_part_power \u003d self.next_part_power"},{"line_number":429,"context_line":"        mismatched_hashes \u003d list()"},{"line_number":430,"context_line":"        for hsh in hashes:"},{"line_number":431,"context_line":"            fname \u003d os.path.join(suff_path, hsh)"}],"source_content_type":"text/x-python","patch_set":12,"id":"a16b21aa_dde859de","line":428,"in_reply_to":"6cea5cea_8e998f41","updated":"2026-05-12 17:09:08.000000000","message":"I\u0027m going to retain the existing filtering logic and add to the if statement a branch for `AUDIT`.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":false,"context_lines":[{"line_number":425,"context_line":"        if self.step \u003d\u003d Step.AUDIT:"},{"line_number":426,"context_line":"            expected_part_power \u003d self.part_power"},{"line_number":427,"context_line":"        else:"},{"line_number":428,"context_line":"            expected_part_power \u003d self.next_part_power"},{"line_number":429,"context_line":"        mismatched_hashes \u003d list()"},{"line_number":430,"context_line":"        for hsh in hashes:"},{"line_number":431,"context_line":"            fname \u003d os.path.join(suff_path, hsh)"}],"source_content_type":"text/x-python","patch_set":12,"id":"7deabd46_d185ee1b","line":428,"in_reply_to":"a16b21aa_dde859de","updated":"2026-08-21 05:26:05.000000000","message":"that\u0027s defensible; preserving existing behavior avoids a whole can-of-worms about if the existing best-effort prev_part_power state was somehow supposed to be trying to make relink more efficient in some imagined double PPI case\n\ncan\u0027t fix all the things at once!","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":551,"context_line":"        expected_part \u003d get_partition_from_path(self.conf[\u0027devices\u0027],"},{"line_number":552,"context_line":"                                                expected_hash_path)"},{"line_number":553,"context_line":"        found_part \u003d get_partition_from_path(self.conf[\u0027devices\u0027], hash_path)"},{"line_number":554,"context_line":"        # TODO: Take in as a CLI argument"},{"line_number":555,"context_line":"        max_num_ppi \u003d 1"},{"line_number":556,"context_line":"        if not _is_ppi_ancestor(found_part,"},{"line_number":557,"context_line":"                                expected_part,"}],"source_content_type":"text/x-python","patch_set":12,"id":"73742103_8bf5871d","line":554,"updated":"2026-05-06 14:47:24.000000000","message":"bah, stupid CLI args vs config values\n\neven if it\u0027s configuable it will need a good default - so you could throw that in a constant at the top","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":551,"context_line":"        expected_part \u003d get_partition_from_path(self.conf[\u0027devices\u0027],"},{"line_number":552,"context_line":"                                                expected_hash_path)"},{"line_number":553,"context_line":"        found_part \u003d get_partition_from_path(self.conf[\u0027devices\u0027], hash_path)"},{"line_number":554,"context_line":"        # TODO: Take in as a CLI argument"},{"line_number":555,"context_line":"        max_num_ppi \u003d 1"},{"line_number":556,"context_line":"        if not _is_ppi_ancestor(found_part,"},{"line_number":557,"context_line":"                                expected_part,"}],"source_content_type":"text/x-python","patch_set":12,"id":"fb2414e7_d8405c06","line":554,"in_reply_to":"73742103_8bf5871d","updated":"2026-05-12 17:09:08.000000000","message":"Done","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":560,"context_line":"                f\"hash_path\u003d{hash_path}: found_part\u003d{found_part} \" +"},{"line_number":561,"context_line":"                f\"not an ancestor of expected_part\u003d{expected_part}: \" +"},{"line_number":562,"context_line":"                \"Skipping quarantine\")"},{"line_number":563,"context_line":"            self.stats[\u0027warnings\u0027] \u003d self.stats.get(\u0027warnings\u0027, 0) + 1"},{"line_number":564,"context_line":"            return"},{"line_number":565,"context_line":""},{"line_number":566,"context_line":"        try:"}],"source_content_type":"text/x-python","patch_set":12,"id":"8cdbceee_476906b3","line":563,"updated":"2026-05-06 14:47:24.000000000","message":"for me tests pass with this diff:\n\n```\ndiff --git a/swift/cli/relinker.py b/swift/cli/relinker.py\nindex 6db1490b5b..08d4b382e0 100644\n--- a/swift/cli/relinker.py\n+++ b/swift/cli/relinker.py\n@@ -560,7 +560,7 @@ class Relinker(object):\n                 f\"hash_path\u003d{hash_path}: found_part\u003d{found_part} \" +\n                 f\"not an ancestor of expected_part\u003d{expected_part}: \" +\n                 \"Skipping quarantine\")\n-            self.stats[\u0027warnings\u0027] \u003d self.stats.get(\u0027warnings\u0027, 0) + 1\n+            self.stats[\u0027warnings\u0027] +\u003d 1\n             return\n \n         try:\n```\n\nI think it\u0027s b/c they\u0027re functionally equivilent - but the `self.stats[\u0027hash_dirs\u0027] +\u003d 1` seems more idiomatic and in-line with surrounding style.\n\nIf there\u0027s  *reason* that it should be written differently (e.g. do we ever \"initialize stats from disk\" (!?) instead of `_zero_stats`) - then a test should help me maintain it to be correct.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":560,"context_line":"                f\"hash_path\u003d{hash_path}: found_part\u003d{found_part} \" +"},{"line_number":561,"context_line":"                f\"not an ancestor of expected_part\u003d{expected_part}: \" +"},{"line_number":562,"context_line":"                \"Skipping quarantine\")"},{"line_number":563,"context_line":"            self.stats[\u0027warnings\u0027] \u003d self.stats.get(\u0027warnings\u0027, 0) + 1"},{"line_number":564,"context_line":"            return"},{"line_number":565,"context_line":""},{"line_number":566,"context_line":"        try:"}],"source_content_type":"text/x-python","patch_set":12,"id":"8793893f_1e97e307","line":563,"in_reply_to":"8cdbceee_476906b3","updated":"2026-05-12 17:09:08.000000000","message":"Thanks for catching this. Existing style is sufficient. Fixed.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":568,"context_line":"            to_dir \u003d diskfile.quarantine_renamer(dev_path, hash_path)"},{"line_number":569,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":570,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":571,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":572,"context_line":"            self.stats[\u0027removed\u0027] +\u003d 1"},{"line_number":573,"context_line":"        except Exception as exc:"},{"line_number":574,"context_line":"            self.logger.exception(\"Could not quarantine \" +"}],"source_content_type":"text/x-python","patch_set":12,"id":"70ef7ca6_883ef4da","line":571,"updated":"2026-05-06 14:47:24.000000000","message":"I\u0027m realizing there\u0027s some case to be made that w/i a reclaim_age we *could* possibly do an immediate relink \u0026 cleanup - but the `self.[next_]part_power` attributes are going to be all messed up.\n\nOperationally I know that b/c `cleanup` typically takes longer than `relinker` and the proxy has been using the `next_part_power` the whole time; that by the time you get to `audit` (even if you do it RIGHT after cleanup) you really don\u0027t care much about the data in the old hash-space.  So I think we should stick with \"quarantine for now\" (as always correct and \"old data\" cases necessary) and keep \"recent enough to repair\" as a kind of \"future enhancement if needed\"","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d4595622d317c8bb7f125e87b70d95abb4d386b5","unresolved":false,"context_lines":[{"line_number":568,"context_line":"            to_dir \u003d diskfile.quarantine_renamer(dev_path, hash_path)"},{"line_number":569,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":570,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":571,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":572,"context_line":"            self.stats[\u0027removed\u0027] +\u003d 1"},{"line_number":573,"context_line":"        except Exception as exc:"},{"line_number":574,"context_line":"            self.logger.exception(\"Could not quarantine \" +"}],"source_content_type":"text/x-python","patch_set":12,"id":"0a780733_e07c6319","line":571,"in_reply_to":"70ef7ca6_883ef4da","updated":"2026-07-30 03:06:03.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":799,"context_line":"                    f\"policy\u003d{policy.name}: \""},{"line_number":800,"context_line":"                    \"Skipping step - PPI in progress: \""},{"line_number":801,"context_line":"                    f\"part_power\u003d{ring.part_power} \" +"},{"line_number":802,"context_line":"                    f\"next_part_power\u003d{ring.next_part_power}\")"},{"line_number":803,"context_line":"                continue"},{"line_number":804,"context_line":""},{"line_number":805,"context_line":"            num_policies +\u003d 1"}],"source_content_type":"text/x-python","patch_set":12,"id":"3ed27b1e_06259351","line":802,"updated":"2026-05-06 14:47:24.000000000","message":"some of these feel more like debug messages","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":799,"context_line":"                    f\"policy\u003d{policy.name}: \""},{"line_number":800,"context_line":"                    \"Skipping step - PPI in progress: \""},{"line_number":801,"context_line":"                    f\"part_power\u003d{ring.part_power} \" +"},{"line_number":802,"context_line":"                    f\"next_part_power\u003d{ring.next_part_power}\")"},{"line_number":803,"context_line":"                continue"},{"line_number":804,"context_line":""},{"line_number":805,"context_line":"            num_policies +\u003d 1"}],"source_content_type":"text/x-python","patch_set":12,"id":"6a6355f4_230d034a","line":802,"in_reply_to":"3ed27b1e_06259351","updated":"2026-05-12 17:09:08.000000000","message":"Makes sense after you explained the rationale. Fixed. Thanks!","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"8a57b2d5bf80adebeda7b8fba114bab485bd9446","unresolved":true,"context_lines":[{"line_number":121,"context_line":""},{"line_number":122,"context_line":"    if max_num_ppi \u003c\u003d 0 or max_num_ppi \u003e 32:"},{"line_number":123,"context_line":"        raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +"},{"line_number":124,"context_line":"                         \"max_num_ppi must be in (0, 32]\")"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    if (found_part, expected_part) \u003d\u003d (0, 0):"},{"line_number":127,"context_line":"        return True"}],"source_content_type":"text/x-python","patch_set":13,"id":"e3347e59_5247558c","line":124,"updated":"2026-05-19 07:25:26.000000000","message":"Shouldn\u0027t this be raised much earlier (ideally at start time), not when a misplaced hash is found? An operator might set this wrongly and only detect the error much later.","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":121,"context_line":""},{"line_number":122,"context_line":"    if max_num_ppi \u003c\u003d 0 or max_num_ppi \u003e 32:"},{"line_number":123,"context_line":"        raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +"},{"line_number":124,"context_line":"                         \"max_num_ppi must be in (0, 32]\")"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    if (found_part, expected_part) \u003d\u003d (0, 0):"},{"line_number":127,"context_line":"        return True"}],"source_content_type":"text/x-python","patch_set":13,"id":"57481375_2f5dd1a8","line":124,"in_reply_to":"1eac5664_5e39e5b6","updated":"2026-08-21 05:26:05.000000000","message":"the only part that still smells is `(0, 32]` being encoded in three different places/ways:\n\n1) `max_audit_history_quarantine_threshold_validator` (via helper as lower\u003d1, upper\u003d32)\n2) conditional \u003c\u003d 0 and \u003e 32\n3) un-reachable error message as `(0, 32]`\n\nFWIW I\u0027d prefer a fourth way:\n\n```\n  if not 0 \u003c x \u003c\u003d 32:\n      raise ValueError(\"x must be in (0, 32]\")\n```\n\n... as more pythonic 😉\n\nBut, they\u0027re all sort of just trying to rephrase the ring code\u0027s rules:\n\n```\n        if part_power \u003e 32:\n            raise ValueError(\"part_power must be at most 32 (was %d)\"\n                             % (part_power,))\n        if part_power \u003c 0:\n            raise ValueError(\"part_power must be at least 0 (was %d)\"\n                             % (part_power,))\n```\n\n... which I guess thinks a ring with part_power\u003d0 *is* reasonable i.e. all hashes belong in part 0\n\nI think duplication is kind of the main reason why \"magic numbers\" are sort of inherently a smell.  Like part of \"magic number is a smell\" is just \"does this rule have a name\" I think the bigger part is \"does everyone follow the same rule\"\n\nAlso this error seems to say `max_num_ppi` sort of more than once:\n```\n\u003e\u003e\u003e relinker._is_ppi_ancestor(0, 0, 0)\nTraceback (most recent call last):\n  File \"\u003cstdin\u003e\", line 1, in \u003cmodule\u003e\n  File \"/home/cgerrard/Workspace/vagrant-swift-all-in-one/swift/swift/cli/relinker.py\", line 124, in _is_ppi_ancestor\n    raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +\nValueError: max_num_ppi\u003d0 max_num_ppi must be in (0, 32]\n```\n\n... which seems like a lot of times to say `max_num_ppi` for something that an operator and maintainer will only ever understand as `max_audit_history_quarantine_threshold`\n\nBut maybe the REAL question we need to be asking here is \"what math in this function is wrong for part power 0 or 33?\" cause I can\u0027t find it?  As best I can tell it treats `max_num_ppi\u003d-1` sort of like `0` and does so correctly.","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":true,"context_lines":[{"line_number":121,"context_line":""},{"line_number":122,"context_line":"    if max_num_ppi \u003c\u003d 0 or max_num_ppi \u003e 32:"},{"line_number":123,"context_line":"        raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +"},{"line_number":124,"context_line":"                         \"max_num_ppi must be in (0, 32]\")"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    if (found_part, expected_part) \u003d\u003d (0, 0):"},{"line_number":127,"context_line":"        return True"}],"source_content_type":"text/x-python","patch_set":13,"id":"d007633f_6be42af5","line":124,"in_reply_to":"57481375_2f5dd1a8","updated":"2026-09-17 04:22:20.000000000","message":"\u003eBut maybe the REAL question we need to be asking here is \"what math in this function is wrong for part power 0 or 33?\nA PPI is valid only if it increases the part power by at least one and never more than 32. The upper bound is loose and can be tightened to `MAX_PART_POWER` - current_part_power, but it should make a difference for this method because of the guard on L132:\n```while expected_part \u003e found_part```","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":false,"context_lines":[{"line_number":121,"context_line":""},{"line_number":122,"context_line":"    if max_num_ppi \u003c\u003d 0 or max_num_ppi \u003e 32:"},{"line_number":123,"context_line":"        raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +"},{"line_number":124,"context_line":"                         \"max_num_ppi must be in (0, 32]\")"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    if (found_part, expected_part) \u003d\u003d (0, 0):"},{"line_number":127,"context_line":"        return True"}],"source_content_type":"text/x-python","patch_set":13,"id":"127a8872_8162ad7a","line":124,"in_reply_to":"d007633f_6be42af5","updated":"2026-09-17 23:40:56.000000000","message":"Like it\u0027s *fine* - I do NOT want to die on this hill ... but, I think it would be pro-social to admit \"reasonable people may disagree on the *necessity* of this level of validation w/i a module private custom built helper designed for use with pre-validated inputs\":\n\nhttps://review.opendev.org/c/openstack/swift/+/966980/22/swift/cli/relinker.py#124\n\n... you can prefer it!  maybe because the math and tests were a little tricky while you were writing it; and so swift will just burn a tiny bit of frames rechecking the same number every call in a 10s of million parttion loop in prod forever - we\u0027re going to rate limit fps anyway; knock yourself out.  👍","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"be733dcd52ccd95dfc5f0a638795c42efc056fa8","unresolved":true,"context_lines":[{"line_number":121,"context_line":""},{"line_number":122,"context_line":"    if max_num_ppi \u003c\u003d 0 or max_num_ppi \u003e 32:"},{"line_number":123,"context_line":"        raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +"},{"line_number":124,"context_line":"                         \"max_num_ppi must be in (0, 32]\")"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    if (found_part, expected_part) \u003d\u003d (0, 0):"},{"line_number":127,"context_line":"        return True"}],"source_content_type":"text/x-python","patch_set":13,"id":"1eac5664_5e39e5b6","line":124,"in_reply_to":"e3347e59_5247558c","updated":"2026-05-20 01:01:32.000000000","message":"I think that\u0027s reasonable. I\u0027ve implemented parse-time check for the parameter. I\u0027ve retained the existing checks in `_is_ppi_ancestor` so that the method enforces its own preconditions.","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"ae302eccc90b75a7ac854ce034dbe009775e924a","unresolved":true,"context_lines":[{"line_number":571,"context_line":""},{"line_number":572,"context_line":"        try:"},{"line_number":573,"context_line":"            dev_path \u003d os.path.join(self.diskfile_mgr.devices, device)"},{"line_number":574,"context_line":"            to_dir \u003d diskfile.quarantine_renamer(dev_path, hash_path)"},{"line_number":575,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":576,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":577,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"}],"source_content_type":"text/x-python","patch_set":13,"id":"b56edbc6_3c86646e","line":574,"updated":"2026-05-19 09:44:50.000000000","message":"This looks like a blocker: using hash_path instead of the filename it will quarantine other (possibly healthy) objects using the same suffix dir. quarantine_renamer expects a file_path, then applies dirname() to it - used for example in line 509 above.\n\nOne possible scenario are two PPIs, where one failed:\n\nObject A: at partition 2 before PPI, after two PPIs: 8\nObject B: at partition 4 before PPI, after two PPIs should be on 16, but it\u0027s stale and still on 8.\n\nA is valid, B is stale and should be quarantined. However, this quarantines A as well, because it quarantines the directory?\n\nJust verified - with two objects in the same suffix dir and at least one of them being stale, both get quarantined, and there is only a single log line:\n\nd_part\u003d114 expected_part\u003d228: /srv/node4/sdb4/objects/114/3ff/393e6de07bd812939a5f2891ad3bd3ff moved to /srv/node4/sdb4/quarantined/objects/3ff","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d4595622d317c8bb7f125e87b70d95abb4d386b5","unresolved":false,"context_lines":[{"line_number":571,"context_line":""},{"line_number":572,"context_line":"        try:"},{"line_number":573,"context_line":"            dev_path \u003d os.path.join(self.diskfile_mgr.devices, device)"},{"line_number":574,"context_line":"            to_dir \u003d diskfile.quarantine_renamer(dev_path, hash_path)"},{"line_number":575,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":576,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":577,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"}],"source_content_type":"text/x-python","patch_set":13,"id":"480f05cf_6a1e54cc","line":574,"in_reply_to":"94037a2f_ab795c5b","updated":"2026-07-30 03:06:03.000000000","message":"I\u0027ve stacked this patch on top of [989469](https://review.opendev.org/c/openstack/swift/+/989469) and I\u0027m now using the `diskfile.quarantine_dir_renamer` API that \"does the right thing\".","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"be733dcd52ccd95dfc5f0a638795c42efc056fa8","unresolved":true,"context_lines":[{"line_number":571,"context_line":""},{"line_number":572,"context_line":"        try:"},{"line_number":573,"context_line":"            dev_path \u003d os.path.join(self.diskfile_mgr.devices, device)"},{"line_number":574,"context_line":"            to_dir \u003d diskfile.quarantine_renamer(dev_path, hash_path)"},{"line_number":575,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":576,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":577,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"}],"source_content_type":"text/x-python","patch_set":13,"id":"94037a2f_ab795c5b","line":574,"in_reply_to":"b56edbc6_3c86646e","updated":"2026-05-20 01:01:32.000000000","message":"Thanks for catching this! It seems that the `quarantine_renamer` is overloaded API in a bunch of other places in the code to do targeted quarantining. I\u0027ve used the same strategy to address this bug and added a corresponding test.","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"8a57b2d5bf80adebeda7b8fba114bab485bd9446","unresolved":true,"context_lines":[{"line_number":575,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":576,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":577,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":578,"context_line":"            self.stats[\u0027removed\u0027] +\u003d 1"},{"line_number":579,"context_line":"        except Exception as exc:"},{"line_number":580,"context_line":"            self.logger.error("},{"line_number":581,"context_line":"                f\"Could not quarantine hash_path\u003d{hash_path}: {exc}\")"}],"source_content_type":"text/x-python","patch_set":13,"id":"b47f89e0_b57ea008","line":578,"updated":"2026-05-19 07:25:26.000000000","message":"This might be misleading to an operator - a quarantined file is still occupying space on disk because it is not removed, but quarantined (moved). I think this needs to be changed?","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"64253b5494337b0b9594b528cef5a27b90d8a5c2","unresolved":true,"context_lines":[{"line_number":575,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":576,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":577,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":578,"context_line":"            self.stats[\u0027removed\u0027] +\u003d 1"},{"line_number":579,"context_line":"        except Exception as exc:"},{"line_number":580,"context_line":"            self.logger.error("},{"line_number":581,"context_line":"                f\"Could not quarantine hash_path\u003d{hash_path}: {exc}\")"}],"source_content_type":"text/x-python","patch_set":13,"id":"b4dff02c_e6d51041","line":578,"in_reply_to":"1c1ab082_1b43e4c7","updated":"2026-05-27 14:31:53.000000000","message":"Hmm, true. We could argue that the object is removed from the accessible storage. I\u0027m interested what others might think about this.","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"be733dcd52ccd95dfc5f0a638795c42efc056fa8","unresolved":true,"context_lines":[{"line_number":575,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":576,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":577,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":578,"context_line":"            self.stats[\u0027removed\u0027] +\u003d 1"},{"line_number":579,"context_line":"        except Exception as exc:"},{"line_number":580,"context_line":"            self.logger.error("},{"line_number":581,"context_line":"                f\"Could not quarantine hash_path\u003d{hash_path}: {exc}\")"}],"source_content_type":"text/x-python","patch_set":13,"id":"1c1ab082_1b43e4c7","line":578,"in_reply_to":"b47f89e0_b57ea008","updated":"2026-05-20 01:01:32.000000000","message":"Interesting point. I agree that `removed` doesn\u0027t capture the exact nature of how we addressed a mis-homed hash, but I don\u0027t know if we would want/need to convey more than what we already do. In the current implementation, each mis-homed hash is handled identically but that might not be the case in the future, and we would still want to account for all them in the same place. Thoughts?","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d4595622d317c8bb7f125e87b70d95abb4d386b5","unresolved":false,"context_lines":[{"line_number":575,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":576,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":577,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":578,"context_line":"            self.stats[\u0027removed\u0027] +\u003d 1"},{"line_number":579,"context_line":"        except Exception as exc:"},{"line_number":580,"context_line":"            self.logger.error("},{"line_number":581,"context_line":"                f\"Could not quarantine hash_path\u003d{hash_path}: {exc}\")"}],"source_content_type":"text/x-python","patch_set":13,"id":"28062878_a9ee19f6","line":578,"in_reply_to":"b4dff02c_e6d51041","updated":"2026-07-30 03:06:03.000000000","message":"Thought about this further and decided to introduce a `quarantined` counter.","commit_id":"48de28ef95c952347c8f7a8f3f18a05221d6d23b"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"359721965b1b9e042b6173754be652c2ae9341af","unresolved":true,"context_lines":[{"line_number":123,"context_line":"        raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +"},{"line_number":124,"context_line":"                         \"max_num_ppi must be in (0, 32]\")"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    if (found_part, expected_part) \u003d\u003d (0, 0):"},{"line_number":127,"context_line":"        return True"},{"line_number":128,"context_line":""},{"line_number":129,"context_line":"    num_ppi \u003d 0"}],"source_content_type":"text/x-python","patch_set":14,"id":"14e41765_6c7726e5","line":126,"updated":"2026-05-26 13:30:44.000000000","message":"Question: Why does `_is_api_ancestor(0,0)` returns True and (5,5) returns False? I\u0027m just trying to understand the logic here:\n\nFor partition 5: (5, 5) -\u003e False, don\u0027t quarantine, seems correct\nFor partition 0: (0,0) -\u003e True, quarantine\n\nIs there a special reason partition 0 needs different handling?","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"64253b5494337b0b9594b528cef5a27b90d8a5c2","unresolved":true,"context_lines":[{"line_number":123,"context_line":"        raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +"},{"line_number":124,"context_line":"                         \"max_num_ppi must be in (0, 32]\")"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    if (found_part, expected_part) \u003d\u003d (0, 0):"},{"line_number":127,"context_line":"        return True"},{"line_number":128,"context_line":""},{"line_number":129,"context_line":"    num_ppi \u003d 0"}],"source_content_type":"text/x-python","patch_set":14,"id":"fa245130_1c27c6cb","line":126,"in_reply_to":"14e41765_6c7726e5","updated":"2026-05-27 14:31:53.000000000","message":"0,0 -\u003e in case of ppi (part power increase) this is always true; it\u0027s a special case because partition 0 shifted will still be 0 - it is always the ancestor.\n\nThe lines below would not return True in this special case - which is wrong.\n\nSee also https://docs.openstack.org/swift/latest/ring_partpower.html - the last section (background) also shows the partition 0 in the example.","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"09c38e484dc7a4b14625df8908f1d1c02136aaee","unresolved":false,"context_lines":[{"line_number":123,"context_line":"        raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +"},{"line_number":124,"context_line":"                         \"max_num_ppi must be in (0, 32]\")"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    if (found_part, expected_part) \u003d\u003d (0, 0):"},{"line_number":127,"context_line":"        return True"},{"line_number":128,"context_line":""},{"line_number":129,"context_line":"    num_ppi \u003d 0"}],"source_content_type":"text/x-python","patch_set":14,"id":"2824f331_f95937cc","line":126,"in_reply_to":"d0d062ad_a337825d","updated":"2026-05-28 10:49:36.000000000","message":"Perfect, thank you very much for the explanation!","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"ed4efb621e61a18a58a039e095ef87777d122dc1","unresolved":true,"context_lines":[{"line_number":123,"context_line":"        raise ValueError(f\"max_num_ppi\u003d{max_num_ppi} \" +"},{"line_number":124,"context_line":"                         \"max_num_ppi must be in (0, 32]\")"},{"line_number":125,"context_line":""},{"line_number":126,"context_line":"    if (found_part, expected_part) \u003d\u003d (0, 0):"},{"line_number":127,"context_line":"        return True"},{"line_number":128,"context_line":""},{"line_number":129,"context_line":"    num_ppi \u003d 0"}],"source_content_type":"text/x-python","patch_set":14,"id":"d0d062ad_a337825d","line":126,"in_reply_to":"fa245130_1c27c6cb","updated":"2026-05-27 14:58:10.000000000","message":"Sorry, my comment was not fully correct. 0 shifted could be 1, but in this case (part 0 found and 0 expected) it is always true - the ancestor of 0 can only be 0.","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"359721965b1b9e042b6173754be652c2ae9341af","unresolved":true,"context_lines":[{"line_number":539,"context_line":"                old_file, new_file)"},{"line_number":540,"context_line":"        return success, created"},{"line_number":541,"context_line":""},{"line_number":542,"context_line":"    def audit_location(self, device, hash_path):"},{"line_number":543,"context_line":"        \"\"\""},{"line_number":544,"context_line":"        Validate if a hash is in the correct partition. Three outcomes:"},{"line_number":545,"context_line":"        - Hash is in a PPI-ancestor partition of expected partition: Hash"}],"source_content_type":"text/x-python","patch_set":14,"id":"6d5d1262_03b61be5","line":542,"updated":"2026-05-26 13:30:44.000000000","message":"Question: following up on my other comment, is there a scenario where `audit_location` would call `_is_ppi_ancestor(0,0)`?","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"64253b5494337b0b9594b528cef5a27b90d8a5c2","unresolved":true,"context_lines":[{"line_number":539,"context_line":"                old_file, new_file)"},{"line_number":540,"context_line":"        return success, created"},{"line_number":541,"context_line":""},{"line_number":542,"context_line":"    def audit_location(self, device, hash_path):"},{"line_number":543,"context_line":"        \"\"\""},{"line_number":544,"context_line":"        Validate if a hash is in the correct partition. Three outcomes:"},{"line_number":545,"context_line":"        - Hash is in a PPI-ancestor partition of expected partition: Hash"}],"source_content_type":"text/x-python","patch_set":14,"id":"96d46453_b4941235","line":542,"in_reply_to":"6d5d1262_03b61be5","updated":"2026-05-27 14:31:53.000000000","message":"Yes, partition 0 will be always 0 - see my comment above.","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"09c38e484dc7a4b14625df8908f1d1c02136aaee","unresolved":false,"context_lines":[{"line_number":539,"context_line":"                old_file, new_file)"},{"line_number":540,"context_line":"        return success, created"},{"line_number":541,"context_line":""},{"line_number":542,"context_line":"    def audit_location(self, device, hash_path):"},{"line_number":543,"context_line":"        \"\"\""},{"line_number":544,"context_line":"        Validate if a hash is in the correct partition. Three outcomes:"},{"line_number":545,"context_line":"        - Hash is in a PPI-ancestor partition of expected partition: Hash"}],"source_content_type":"text/x-python","patch_set":14,"id":"b8ae2738_602c7e1b","line":542,"in_reply_to":"96d46453_b4941235","updated":"2026-05-28 10:49:36.000000000","message":"Done","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"359721965b1b9e042b6173754be652c2ae9341af","unresolved":true,"context_lines":[{"line_number":574,"context_line":"            # HACK: works around quarantine_renamer\u0027s awareness of object"},{"line_number":575,"context_line":"            # server\u0027s on-disk layout that results in quarantining the parent"},{"line_number":576,"context_line":"            # directory of the passed path."},{"line_number":577,"context_line":"            to_dir \u003d diskfile.quarantine_renamer("},{"line_number":578,"context_line":"                dev_path, os.path.join(hash_path, \u0027fake-file\u0027))"},{"line_number":579,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":580,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"}],"source_content_type":"text/x-python","patch_set":14,"id":"095e194e_f663349e","line":577,"updated":"2026-05-26 13:30:44.000000000","message":"Suggestion: This uses the \"fake-file\" hack that patch 989469 is eliminating. Consider adding a TODO or note about the dependency relationship between these patches.","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"64253b5494337b0b9594b528cef5a27b90d8a5c2","unresolved":true,"context_lines":[{"line_number":574,"context_line":"            # HACK: works around quarantine_renamer\u0027s awareness of object"},{"line_number":575,"context_line":"            # server\u0027s on-disk layout that results in quarantining the parent"},{"line_number":576,"context_line":"            # directory of the passed path."},{"line_number":577,"context_line":"            to_dir \u003d diskfile.quarantine_renamer("},{"line_number":578,"context_line":"                dev_path, os.path.join(hash_path, \u0027fake-file\u0027))"},{"line_number":579,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":580,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"}],"source_content_type":"text/x-python","patch_set":14,"id":"6a9dc871_c7f767bc","line":577,"in_reply_to":"095e194e_f663349e","updated":"2026-05-27 14:31:53.000000000","message":"Good point. Or just remove the comment+workaround in 989469 itself.","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d4595622d317c8bb7f125e87b70d95abb4d386b5","unresolved":false,"context_lines":[{"line_number":574,"context_line":"            # HACK: works around quarantine_renamer\u0027s awareness of object"},{"line_number":575,"context_line":"            # server\u0027s on-disk layout that results in quarantining the parent"},{"line_number":576,"context_line":"            # directory of the passed path."},{"line_number":577,"context_line":"            to_dir \u003d diskfile.quarantine_renamer("},{"line_number":578,"context_line":"                dev_path, os.path.join(hash_path, \u0027fake-file\u0027))"},{"line_number":579,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":580,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"}],"source_content_type":"text/x-python","patch_set":14,"id":"0001e4a2_432088e3","line":577,"in_reply_to":"6a9dc871_c7f767bc","updated":"2026-07-30 03:06:03.000000000","message":"I decided to stack the patch on top of [989469](https://review.opendev.org/c/openstack/swift/+/989469) and make use of the updated the API.","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"359721965b1b9e042b6173754be652c2ae9341af","unresolved":true,"context_lines":[{"line_number":580,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":581,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":582,"context_line":"            self.stats[\u0027removed\u0027] +\u003d 1"},{"line_number":583,"context_line":"        except Exception as exc:"},{"line_number":584,"context_line":"            self.logger.error("},{"line_number":585,"context_line":"                f\"Could not quarantine hash_path\u003d{hash_path}: {exc}\")"},{"line_number":586,"context_line":"            self.stats[\u0027errors\u0027] +\u003d 1"}],"source_content_type":"text/x-python","patch_set":14,"id":"0c35d1ce_390498fa","line":583,"updated":"2026-05-26 13:30:44.000000000","message":"Suggestion: Consider narrowing from\n\n```\nexcept Exception as exc:\n    ...\n```\n\nto something like\n\n```\nexcept (OSError, IOError) as exc:\n    ...\n```\n\nI think this follows Swift pattern better","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"64253b5494337b0b9594b528cef5a27b90d8a5c2","unresolved":true,"context_lines":[{"line_number":580,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":581,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":582,"context_line":"            self.stats[\u0027removed\u0027] +\u003d 1"},{"line_number":583,"context_line":"        except Exception as exc:"},{"line_number":584,"context_line":"            self.logger.error("},{"line_number":585,"context_line":"                f\"Could not quarantine hash_path\u003d{hash_path}: {exc}\")"},{"line_number":586,"context_line":"            self.stats[\u0027errors\u0027] +\u003d 1"}],"source_content_type":"text/x-python","patch_set":14,"id":"adffe85d_8e63c22c","line":583,"in_reply_to":"0c35d1ce_390498fa","updated":"2026-05-27 14:31:53.000000000","message":"I think this is the right choice in this case? It\u0027s a catchall exception to ensure it is always logged and the error counter is raised.","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"09c38e484dc7a4b14625df8908f1d1c02136aaee","unresolved":false,"context_lines":[{"line_number":580,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":581,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":582,"context_line":"            self.stats[\u0027removed\u0027] +\u003d 1"},{"line_number":583,"context_line":"        except Exception as exc:"},{"line_number":584,"context_line":"            self.logger.error("},{"line_number":585,"context_line":"                f\"Could not quarantine hash_path\u003d{hash_path}: {exc}\")"},{"line_number":586,"context_line":"            self.stats[\u0027errors\u0027] +\u003d 1"}],"source_content_type":"text/x-python","patch_set":14,"id":"dfae8cbd_4c7298f8","line":583,"in_reply_to":"adffe85d_8e63c22c","updated":"2026-05-28 10:49:36.000000000","message":"Acknowledged","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"724cd57f9e47cdb103cc91854987e442d8446ffa","unresolved":true,"context_lines":[{"line_number":135,"context_line":"    return expected_part \u003d\u003d found_part and num_ppi \u003e 0"},{"line_number":136,"context_line":""},{"line_number":137,"context_line":""},{"line_number":138,"context_line":"class Relinker(object):"},{"line_number":139,"context_line":"    def __init__(self, conf, logger, device_list\u003dNone, step\u003dStep.RELINK):"},{"line_number":140,"context_line":"        self.conf \u003d conf"},{"line_number":141,"context_line":"        self.recon_cache \u003d os.path.join(self.conf[\u0027recon_cache_path\u0027],"}],"source_content_type":"text/x-python","patch_set":17,"id":"7ec2a217_0624a931","line":138,"updated":"2026-07-31 04:32:33.000000000","message":"Now that we\u0027re using a step that is an enum and not just a bool whose name was self documenting. I think we really need to add a docstring for Relinker constructor now, so there is some param explanations, and hints to users/callers of the class, esp around the step being an enum.","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d9178487d573e2affa092a411be145bd8b3b38cc","unresolved":false,"context_lines":[{"line_number":135,"context_line":"    return expected_part \u003d\u003d found_part and num_ppi \u003e 0"},{"line_number":136,"context_line":""},{"line_number":137,"context_line":""},{"line_number":138,"context_line":"class Relinker(object):"},{"line_number":139,"context_line":"    def __init__(self, conf, logger, device_list\u003dNone, step\u003dStep.RELINK):"},{"line_number":140,"context_line":"        self.conf \u003d conf"},{"line_number":141,"context_line":"        self.recon_cache \u003d os.path.join(self.conf[\u0027recon_cache_path\u0027],"}],"source_content_type":"text/x-python","patch_set":17,"id":"298026c7_80c73de6","line":138,"in_reply_to":"7ec2a217_0624a931","updated":"2026-08-03 21:20:26.000000000","message":"I think that\u0027s reasonable. I\u0027m working on subcommand-level docs for the man pages. I\u0027ll distill something for the constructor once that work is done.","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":7233,"name":"Matthew Oliver","email":"matt@oliver.net.au","username":"mattoliverau"},"change_message_id":"724cd57f9e47cdb103cc91854987e442d8446ffa","unresolved":true,"context_lines":[{"line_number":571,"context_line":"            return"},{"line_number":572,"context_line":""},{"line_number":573,"context_line":"        try:"},{"line_number":574,"context_line":"            dev_path \u003d os.path.join(self.diskfile_mgr.devices, device)"},{"line_number":575,"context_line":"            to_dir \u003d diskfile.quarantine_dir_renamer(dev_path, hash_path)"},{"line_number":576,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":577,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"}],"source_content_type":"text/x-python","patch_set":17,"id":"40e487c4_d36a6836","line":574,"updated":"2026-07-31 04:32:33.000000000","message":"Love it, but I do wonder if we want an dry-run option, that logs everything but skips quarantine? Maybe I\u0027m just too cautious, so happy to be over ruled. Just just a thought, just in case it moves more then we expect.","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d9178487d573e2affa092a411be145bd8b3b38cc","unresolved":true,"context_lines":[{"line_number":571,"context_line":"            return"},{"line_number":572,"context_line":""},{"line_number":573,"context_line":"        try:"},{"line_number":574,"context_line":"            dev_path \u003d os.path.join(self.diskfile_mgr.devices, device)"},{"line_number":575,"context_line":"            to_dir \u003d diskfile.quarantine_dir_renamer(dev_path, hash_path)"},{"line_number":576,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":577,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"}],"source_content_type":"text/x-python","patch_set":17,"id":"e7866a40_238d1275","line":574,"in_reply_to":"40e487c4_d36a6836","updated":"2026-08-03 21:20:26.000000000","message":"This seems to suggest that we don\u0027t have confidence in the patch/workflow, and I\u0027d rather fix that here than implement dry-run (do we also implement it for relink/cleanup?). If we\u0027re interested in identifying what hashes are candidates for quarantining, then perhaps we can achieve that via logging/telemetry.","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":false,"context_lines":[{"line_number":571,"context_line":"            return"},{"line_number":572,"context_line":""},{"line_number":573,"context_line":"        try:"},{"line_number":574,"context_line":"            dev_path \u003d os.path.join(self.diskfile_mgr.devices, device)"},{"line_number":575,"context_line":"            to_dir \u003d diskfile.quarantine_dir_renamer(dev_path, hash_path)"},{"line_number":576,"context_line":"            self.logger.info(f\"found_part\u003d{found_part} \" +"},{"line_number":577,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"}],"source_content_type":"text/x-python","patch_set":17,"id":"f162dc9f_066c3cbd","line":574,"in_reply_to":"e7866a40_238d1275","updated":"2026-09-17 23:40:56.000000000","message":"I think we used a gizmo to spot-check/dry-run\n\nI have a lot of confidence in this approach.\n\nWe do NOT have a relinker dry-run for relink/cleanup - or any other swift consistency daemon that I can think of; except for maybe sort of the sharder if you kind of squnit - it\u0027s not *called* dry-run tho just kind of \"do nothing by deafult with some candidate telemetry\".","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":6968,"name":"Christian Schwede","email":"cschwede@nvidia.com","username":"cschwede"},"change_message_id":"9657da57dae52d14c0fb019ea7372a8a66e2a5cf","unresolved":true,"context_lines":[{"line_number":577,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":578,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":579,"context_line":"            self.stats[\u0027quarantined\u0027] +\u003d 1"},{"line_number":580,"context_line":"        except Exception as exc:"},{"line_number":581,"context_line":"            self.logger.error("},{"line_number":582,"context_line":"                f\"Could not quarantine hash_path\u003d{hash_path}: {exc}\")"},{"line_number":583,"context_line":"            self.stats[\u0027errors\u0027] +\u003d 1"}],"source_content_type":"text/x-python","patch_set":17,"id":"f19aa40e_048172f1","line":580,"updated":"2026-07-31 14:01:58.000000000","message":"This needs to be ```except (Exception, LockTimeout) as exc:``` , otherwise a ```LockTimeout```is not caught and the whole run might get killed (see line 665 and 702 as well)\n\n```\n\u003e\u003e\u003e from swift.common.exceptions import LockTimeout\n\u003e\u003e\u003e issubclass(LockTimeout, Exception)\nFalse\n```","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d9178487d573e2affa092a411be145bd8b3b38cc","unresolved":false,"context_lines":[{"line_number":577,"context_line":"                             f\"expected_part\u003d{expected_part}: \" +"},{"line_number":578,"context_line":"                             f\"{hash_path} moved to {to_dir}\")"},{"line_number":579,"context_line":"            self.stats[\u0027quarantined\u0027] +\u003d 1"},{"line_number":580,"context_line":"        except Exception as exc:"},{"line_number":581,"context_line":"            self.logger.error("},{"line_number":582,"context_line":"                f\"Could not quarantine hash_path\u003d{hash_path}: {exc}\")"},{"line_number":583,"context_line":"            self.stats[\u0027errors\u0027] +\u003d 1"}],"source_content_type":"text/x-python","patch_set":17,"id":"06185ad7_11953380","line":580,"in_reply_to":"f19aa40e_048172f1","updated":"2026-08-03 21:20:26.000000000","message":"Fixed. Thanks!","commit_id":"5b3a81f091b3416ac30f2c5f89c6047fb7a0de59"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":108,"context_line":"        \u0027stats\u0027: _zero_stats()}"},{"line_number":109,"context_line":""},{"line_number":110,"context_line":""},{"line_number":111,"context_line":"def _is_ppi_ancestor(found_part, expected_part, max_num_ppi\u003d32):"},{"line_number":112,"context_line":"    \"\"\""},{"line_number":113,"context_line":"    Determine if `expected_part` descended from `found_part` via a sequence of"},{"line_number":114,"context_line":"    partition power increase operations. The search is limited to at most"}],"source_content_type":"text/x-python","patch_set":19,"id":"6165b182_e6a41fd3","line":111,"updated":"2026-08-21 05:26:05.000000000","message":"why not `max_num_ppi\u003dDEFAULT_MAX_AUDIT_HISTORY_QUARANTINE_THRESHOLD` or maybe even don\u0027t make it kwarg?","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":108,"context_line":"        \u0027stats\u0027: _zero_stats()}"},{"line_number":109,"context_line":""},{"line_number":110,"context_line":""},{"line_number":111,"context_line":"def _is_ppi_ancestor(found_part, expected_part, max_num_ppi\u003d32):"},{"line_number":112,"context_line":"    \"\"\""},{"line_number":113,"context_line":"    Determine if `expected_part` descended from `found_part` via a sequence of"},{"line_number":114,"context_line":"    partition power increase operations. The search is limited to at most"}],"source_content_type":"text/x-python","patch_set":19,"id":"5680ad90_070150f8","line":111,"in_reply_to":"6165b182_e6a41fd3","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":295,"context_line":"                          if int(part) \u003c 2 ** (self.part_power - 1)]"},{"line_number":296,"context_line":"        else:"},{"line_number":297,"context_line":"            # We\u0027ve handled all values of the `Step` enum. Nothing to do here"},{"line_number":298,"context_line":"            pass"},{"line_number":299,"context_line":""},{"line_number":300,"context_line":"        # Format: { \u0027part\u0027: processed }"},{"line_number":301,"context_line":"        if self.states[\"state\"]:"}],"source_content_type":"text/x-python","patch_set":19,"id":"065ff6de_2267f74b","line":298,"updated":"2026-08-21 05:26:05.000000000","message":"more obvious as `raise ValueError(\u0027unexpected step: %s\u0027 % self.step)`\n\nProbably gets simpler when we dump `if \"prev_part_power\" in self.states:`\n\nand just make \n\n```\n        if self.step \u003d\u003d Step.AUDIT:\n            expected_part_power \u003d self.part_power\n        else:\n            expected_part_power \u003d self.next_part_power\n```\n\nlike a property so we can unconditionally:\n\n```\n# relinker only needs to work on the lower half\npartitions \u003d [p for p in parts if int(p) \u003c 2 ** (self.expected_part_power - 1)]\n```","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":295,"context_line":"                          if int(part) \u003c 2 ** (self.part_power - 1)]"},{"line_number":296,"context_line":"        else:"},{"line_number":297,"context_line":"            # We\u0027ve handled all values of the `Step` enum. Nothing to do here"},{"line_number":298,"context_line":"            pass"},{"line_number":299,"context_line":""},{"line_number":300,"context_line":"        # Format: { \u0027part\u0027: processed }"},{"line_number":301,"context_line":"        if self.states[\"state\"]:"}],"source_content_type":"text/x-python","patch_set":19,"id":"71664296_698be6ce","line":298,"in_reply_to":"065ff6de_2267f74b","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":810,"context_line":"                                      \"Skipping step - PPI in progress: \""},{"line_number":811,"context_line":"                                      f\"part_power\u003d{ring.part_power} \" +"},{"line_number":812,"context_line":"                                      f\"next_part_power\u003d{ring.next_part_power}\""},{"line_number":813,"context_line":"                                      )"},{"line_number":814,"context_line":"                    continue"},{"line_number":815,"context_line":"            else:"},{"line_number":816,"context_line":"                if not ring.next_part_power:"}],"source_content_type":"text/x-python","patch_set":19,"id":"ccc10b97_f640bfb1","line":813,"updated":"2026-08-21 05:26:05.000000000","message":"1) generally I prefer policy.idx as the least ambigous way for an operator to reason about which policy they are dealing with - the are less mutable than policy.name\n\ne.g. in the reconstructor:\n\n```\n  self.logger.info(\n      \"Ring change detected for policy %d (%s). Aborting \"\n      \"current reconstruction pass for this policy.\",\n      part_info[\u0027policy\u0027].idx, part_info[\u0027policy\u0027].name)\n```\n\n`proxy.server` has a cute helper it uses for per-policy configuration:\n\n```\ndef _label_for_policy(policy):\n    if policy is not None:\n        return \u0027policy %s (%s)\u0027 % (policy.idx, policy.name)\n    return \u0027(default)\u0027\n```\n\n2) \"Skipping step\" seems overly generic and verbose, we\u0027re already in the `step \u003d\u003d audit` branch and we probably have a logger prefixed w/ `step\u003dAUDIT` too\n\n`Skipping policy 1 (ec) while PPI in progress` might be good enough, maybe useful at info+ level considering that audit intends to make `--policy` requried?","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":810,"context_line":"                                      \"Skipping step - PPI in progress: \""},{"line_number":811,"context_line":"                                      f\"part_power\u003d{ring.part_power} \" +"},{"line_number":812,"context_line":"                                      f\"next_part_power\u003d{ring.next_part_power}\""},{"line_number":813,"context_line":"                                      )"},{"line_number":814,"context_line":"                    continue"},{"line_number":815,"context_line":"            else:"},{"line_number":816,"context_line":"                if not ring.next_part_power:"}],"source_content_type":"text/x-python","patch_set":19,"id":"be4b89c5_57bf638d","line":813,"in_reply_to":"ccc10b97_f640bfb1","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":816,"context_line":"                if not ring.next_part_power:"},{"line_number":817,"context_line":"                    continue"},{"line_number":818,"context_line":"                part_power_increased \u003d ring.next_part_power \u003d\u003d ring.part_power"},{"line_number":819,"context_line":"                do_cleanup \u003d self.step \u003d\u003d Step.CLEANUP"},{"line_number":820,"context_line":"                if do_cleanup !\u003d part_power_increased:"},{"line_number":821,"context_line":"                    continue"},{"line_number":822,"context_line":""}],"source_content_type":"text/x-python","patch_set":19,"id":"bc4fa183_d28245bb","line":819,"updated":"2026-08-21 05:26:05.000000000","message":"I support the re-creating of the pre-existing do_cleanup varialbe not becuause I think the makes this code any easier to grok - but b/c it\u0027s easier to see that\u0027s equivilent to the pre-existing obfuscated logic\n\nFWIW I find this table useful:\n\n```\n   Phase                               ring.part_power    ring.next_part_power    Relinker action\n  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━━━━  ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n   Normal/stable                                     P                    None    Nothing\n  ──────────────────────────────────  ─────────────────  ──────────────────────  ───────────────────────────────────────────────────────────────────────────────────────────────────────────\n   prepare_increase_partition_power                  P                   P + 1    relink: hard-link every object from its old P-power partition into its future P + 1 partition\n  ──────────────────────────────────  ─────────────────  ──────────────────────  ───────────────────────────────────────────────────────────────────────────────────────────────────────────\n   increase_partition_power                      P + 1                   P + 1    cleanup: verify links/data in the current P + 1 location, then remove the old copies in P-power locations\n  ──────────────────────────────────  ─────────────────  ──────────────────────  ───────────────────────────────────────────────────────────────────────────────────────────────────────────\n   finish_increase_partition_power               P + 1                    None    Nothing; PPI is complete\n```\n\nto help think through when relink or cleanup should skip which policies.","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":816,"context_line":"                if not ring.next_part_power:"},{"line_number":817,"context_line":"                    continue"},{"line_number":818,"context_line":"                part_power_increased \u003d ring.next_part_power \u003d\u003d ring.part_power"},{"line_number":819,"context_line":"                do_cleanup \u003d self.step \u003d\u003d Step.CLEANUP"},{"line_number":820,"context_line":"                if do_cleanup !\u003d part_power_increased:"},{"line_number":821,"context_line":"                    continue"},{"line_number":822,"context_line":""}],"source_content_type":"text/x-python","patch_set":19,"id":"fdd4c792_0512f302","line":819,"in_reply_to":"bc4fa183_d28245bb","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":828,"context_line":"        self._update_recon(force_dump\u003dTrue)"},{"line_number":829,"context_line":"        if not num_policies:"},{"line_number":830,"context_line":"            self.logger.warning("},{"line_number":831,"context_line":"                \"No policy found to increase the partition power.\")"},{"line_number":832,"context_line":"            return EXIT_NO_APPLICABLE_POLICY"},{"line_number":833,"context_line":""},{"line_number":834,"context_line":"        if self.total_errors \u003e 0:"}],"source_content_type":"text/x-python","patch_set":19,"id":"6051e1e4_65559c60","line":831,"updated":"2026-08-21 05:26:05.000000000","message":"this logging might get sort of confusing - but at least it might hint at running with `log_level\u003dDEBUG` if your relink audit doesn\u0027t seem to do anything b/c you haven\u0027t `finish_increase_partition_power` yet!","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":828,"context_line":"        self._update_recon(force_dump\u003dTrue)"},{"line_number":829,"context_line":"        if not num_policies:"},{"line_number":830,"context_line":"            self.logger.warning("},{"line_number":831,"context_line":"                \"No policy found to increase the partition power.\")"},{"line_number":832,"context_line":"            return EXIT_NO_APPLICABLE_POLICY"},{"line_number":833,"context_line":""},{"line_number":834,"context_line":"        if self.total_errors \u003e 0:"}],"source_content_type":"text/x-python","patch_set":19,"id":"4ca90680_8c701007","line":831,"in_reply_to":"6051e1e4_65559c60","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":868,"context_line":"        log_method("},{"line_number":869,"context_line":"            \u0027%d hash dirs processed (%d files, %d linked, \u0027"},{"line_number":870,"context_line":"            \u0027%d removed, %d quarantined, %d errors)\u0027, hash_dirs, files,"},{"line_number":871,"context_line":"            linked, removed, quarantined,"},{"line_number":872,"context_line":"            action_errors + listdir_errors)"},{"line_number":873,"context_line":""},{"line_number":874,"context_line":"        return status"}],"source_content_type":"text/x-python","patch_set":19,"id":"1cc2309c_bd95f5c8","line":871,"updated":"2026-08-21 05:26:05.000000000","message":"will relink \u0026 cleanup always log `0 quarantined` - and we never log warnings counts?","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":868,"context_line":"        log_method("},{"line_number":869,"context_line":"            \u0027%d hash dirs processed (%d files, %d linked, \u0027"},{"line_number":870,"context_line":"            \u0027%d removed, %d quarantined, %d errors)\u0027, hash_dirs, files,"},{"line_number":871,"context_line":"            linked, removed, quarantined,"},{"line_number":872,"context_line":"            action_errors + listdir_errors)"},{"line_number":873,"context_line":""},{"line_number":874,"context_line":"        return status"}],"source_content_type":"text/x-python","patch_set":19,"id":"bef74260_7421138d","line":871,"in_reply_to":"1cc2309c_bd95f5c8","updated":"2026-09-17 04:22:20.000000000","message":"They will. I\u0027ve added a warnings count.","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":984,"context_line":"    if lower_bound is not None and int_value \u003c lower_bound:"},{"line_number":985,"context_line":"        raise ValueError(f\"value\u003d{int_value} must be \u003e\u003d {lower_bound}\")"},{"line_number":986,"context_line":"    if upper_bound is not None and int_value \u003e upper_bound:"},{"line_number":987,"context_line":"        raise ValueError(f\"value\u003d{int_value} must be \u003c\u003d {upper_bound}\")"},{"line_number":988,"context_line":""},{"line_number":989,"context_line":"    return int_value"},{"line_number":990,"context_line":""}],"source_content_type":"text/x-python","patch_set":19,"id":"289164dd_824f0635","line":987,"updated":"2026-08-21 05:26:05.000000000","message":"existing examples from common.utils.config helpers are varied:\n\n```\n        raise ValueError(\u0027Value must be a non-negative %s, not \"%s\".\u0027\n                         % (expected_type_description, value))\n        raise ValueError(\n            \u0027Config option must be an positive int number, not \"%s\".\u0027 % value)\n        raise ValueError(\n            \u0027Config option must be a positive float number, not \"%s\".\u0027 % value)\n```\n\ncloest existing use-case is `config_postive_float_value`:\n\n```\n        min_ \u003d \u0027, greater than %s\u0027 % minimum if minimum is not None else \u0027\u0027\n        max_ \u003d \u0027, less than %s\u0027 % maximum if maximum is not None else \u0027\u0027\n        raise ValueError(\u0027Config option must be a number%s%s, not \"%s\".\u0027 %\n                         (min_, max_, value))\n```\n\nI don\u0027t think implementing this as a generic helper here instead of in `common.utils.config` is really doing us much favors in terms of avoiding bike shedding.","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":984,"context_line":"    if lower_bound is not None and int_value \u003c lower_bound:"},{"line_number":985,"context_line":"        raise ValueError(f\"value\u003d{int_value} must be \u003e\u003d {lower_bound}\")"},{"line_number":986,"context_line":"    if upper_bound is not None and int_value \u003e upper_bound:"},{"line_number":987,"context_line":"        raise ValueError(f\"value\u003d{int_value} must be \u003c\u003d {upper_bound}\")"},{"line_number":988,"context_line":""},{"line_number":989,"context_line":"    return int_value"},{"line_number":990,"context_line":""}],"source_content_type":"text/x-python","patch_set":19,"id":"db351568_80e43836","line":987,"in_reply_to":"289164dd_824f0635","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":1096,"context_line":"                                            DEFAULT_STATS_INTERVAL)),"},{"line_number":1097,"context_line":"        \u0027max_audit_history_quarantine_threshold\u0027: ("},{"line_number":1098,"context_line":"            args.max_audit_history_quarantine_threshold or"},{"line_number":1099,"context_line":"            max_audit_history_quarantine_threshold_validator(conf.get("},{"line_number":1100,"context_line":"                \u0027max_audit_history_quarantine_threshold\u0027,"},{"line_number":1101,"context_line":"                DEFAULT_MAX_AUDIT_HISTORY_QUARANTINE_THRESHOLD))),"},{"line_number":1102,"context_line":"        \u0027clobber_hardlink_collisions\u0027: ("}],"source_content_type":"text/x-python","patch_set":19,"id":"d3aa2698_f7693f13","line":1099,"updated":"2026-08-21 05:26:05.000000000","message":"`args or validate(conf.get(...))` seems to eschew the typical `validate(args or conf.get(...))` ordering\n\nIs the difference necessary or useful?","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":1096,"context_line":"                                            DEFAULT_STATS_INTERVAL)),"},{"line_number":1097,"context_line":"        \u0027max_audit_history_quarantine_threshold\u0027: ("},{"line_number":1098,"context_line":"            args.max_audit_history_quarantine_threshold or"},{"line_number":1099,"context_line":"            max_audit_history_quarantine_threshold_validator(conf.get("},{"line_number":1100,"context_line":"                \u0027max_audit_history_quarantine_threshold\u0027,"},{"line_number":1101,"context_line":"                DEFAULT_MAX_AUDIT_HISTORY_QUARANTINE_THRESHOLD))),"},{"line_number":1102,"context_line":"        \u0027clobber_hardlink_collisions\u0027: ("}],"source_content_type":"text/x-python","patch_set":19,"id":"f7d989b5_075b32e2","line":1099,"in_reply_to":"d3aa2698_f7693f13","updated":"2026-09-17 04:22:20.000000000","message":"Nope. Fixed.","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":1103,"context_line":"            args.clobber_hardlink_collisions or"},{"line_number":1104,"context_line":"            config_true_value(conf.get(\u0027clobber_hardlink_collisions\u0027,"},{"line_number":1105,"context_line":"                                       \u0027false\u0027))),"},{"line_number":1106,"context_line":"    })"},{"line_number":1107,"context_line":"    return parallel_process(args.action, conf, logger, args.device_list)"}],"source_content_type":"text/x-python","patch_set":19,"id":"34361c22_3547baf9","line":1106,"updated":"2026-08-21 05:26:05.000000000","message":"you could MAYBE side step some of the bike-shedding about how to do generic config validation and just focus on the necessary/useful:\n\n```\n# blow up early on bad config\n_is_ppi_ancestor(0, 0, conf[\u0027max_audit_history_quarantine_threshold\u0027])\n```","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":1103,"context_line":"            args.clobber_hardlink_collisions or"},{"line_number":1104,"context_line":"            config_true_value(conf.get(\u0027clobber_hardlink_collisions\u0027,"},{"line_number":1105,"context_line":"                                       \u0027false\u0027))),"},{"line_number":1106,"context_line":"    })"},{"line_number":1107,"context_line":"    return parallel_process(args.action, conf, logger, args.device_list)"}],"source_content_type":"text/x-python","patch_set":19,"id":"a3fd9643_b07dae80","line":1106,"in_reply_to":"34361c22_3547baf9","updated":"2026-09-17 04:22:20.000000000","message":"The validator is now specific to what we\u0027re try to do. Hopefully it\u0027s a bit less bike-shedding now.","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":510,"context_line":"        :param hash_path: old hash directory path"},{"line_number":511,"context_line":"        :param new_hash_path: new hash directory path"},{"line_number":512,"context_line":"        \"\"\""},{"line_number":513,"context_line":"        self.stats[\u0027hash_dirs\u0027] +\u003d 1"},{"line_number":514,"context_line":""},{"line_number":515,"context_line":"        # Get on disk data for new and old locations, cleaning up any"},{"line_number":516,"context_line":"        # reclaimable or obsolete files in each. The new location is"}],"source_content_type":"text/x-python","patch_set":22,"id":"869824e5_93d0cb25","side":"PARENT","line":513,"updated":"2026-09-17 23:40:56.000000000","message":"this got moved outside into the loop and I think it\u0027s a welcome improvement!","commit_id":"bd664103928dde998f7b79ec73c572153c8b1b52"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":558,"context_line":"        expected_hash_path \u003d replace_partition_in_path("},{"line_number":559,"context_line":"            self.conf[\u0027devices\u0027], hash_path, self.part_power)"},{"line_number":560,"context_line":"        if expected_hash_path \u003d\u003d hash_path:"},{"line_number":561,"context_line":"            return"},{"line_number":562,"context_line":""},{"line_number":563,"context_line":"        expected_part \u003d get_partition_from_path(self.conf[\u0027devices\u0027],"},{"line_number":564,"context_line":"                                                expected_hash_path)"}],"source_content_type":"text/x-python","patch_set":22,"id":"a640761c_64a02072","line":561,"updated":"2026-09-17 23:40:56.000000000","message":"IMHO this guard is clarifying even tho it never runs","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":566,"context_line":"        if not _is_ppi_ancestor(found_part,"},{"line_number":567,"context_line":"                                expected_part,"},{"line_number":568,"context_line":"                                self.conf["},{"line_number":569,"context_line":"                                    \u0027max_audit_history_quarantine_threshold\u0027]):"},{"line_number":570,"context_line":"            self.logger.warning("},{"line_number":571,"context_line":"                f\"hash_path\u003d{hash_path}: found_part\u003d{found_part} \" +"},{"line_number":572,"context_line":"                f\"not an ancestor of expected_part\u003d{expected_part}: \" +"}],"source_content_type":"text/x-python","patch_set":22,"id":"926c507e_65d84640","line":569,"updated":"2026-09-17 23:40:56.000000000","message":"as best I can tell, reaching directly into `self.conf` is a common relinker pattern - it\u0027s just the same:\n\n```\n  self.max_audit_history_quarantine_threshold \u003d \\\n      conf[\u0027max_audit_history_quarantine_threshold\u0027]\n```\n\nI\u0027m used to in other swift daemons.","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":753,"context_line":"                new_hash_path \u003d replace_partition_in_path("},{"line_number":754,"context_line":"                    self.conf[\u0027devices\u0027], hash_path, self.next_part_power)"},{"line_number":755,"context_line":"                if new_hash_path \u003d\u003d hash_path:"},{"line_number":756,"context_line":"                    continue"},{"line_number":757,"context_line":"                self.process_location(device, hash_path, new_hash_path)"},{"line_number":758,"context_line":""},{"line_number":759,"context_line":"        # any unmounted devices don\u0027t trigger the pre_device trigger."}],"source_content_type":"text/x-python","patch_set":22,"id":"a4d42fa9_2e6fa6a5","line":756,"updated":"2026-09-17 23:40:56.000000000","message":"I love how the no existing unittests had to change to expect the hash_dirs increment moved above this continue b/c ever E2E test has already pre-filtered.\n\nthe pre-existing code sucks; i\u0027m surprised we didn\u0027t manage to bring fixing it to be sensible in-scope especially since we have to indent it.","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":1076,"context_line":"        logger \u003d SwiftLogAdapter(logging.getLogger(), server\u003d\u0027relinker\u0027)"},{"line_number":1077,"context_line":""},{"line_number":1078,"context_line":"    if args.action \u003d\u003d Step.AUDIT.value and not args.policies:"},{"line_number":1079,"context_line":"        parser.error(\"--policy is required when running in audit mode\")"},{"line_number":1080,"context_line":""},{"line_number":1081,"context_line":"    conf.update({"},{"line_number":1082,"context_line":"        \u0027swift_dir\u0027: args.swift_dir or conf.get(\u0027swift_dir\u0027, \u0027/etc/swift\u0027),"}],"source_content_type":"text/x-python","patch_set":22,"id":"67813ef2_d77b3ea3","line":1079,"updated":"2026-09-17 23:40:56.000000000","message":"if you quized me I would have said argparse.error will exit 1\n\nhttps://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.error","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":1100,"context_line":"        part_power_increment_validator("},{"line_number":1101,"context_line":"            args.max_audit_history_quarantine_threshold or"},{"line_number":1102,"context_line":"            conf.get(\u0027max_audit_history_quarantine_threshold\u0027,"},{"line_number":1103,"context_line":"                     DEFAULT_MAX_AUDIT_HISTORY_QUARANTINE_THRESHOLD)),"},{"line_number":1104,"context_line":"        \u0027clobber_hardlink_collisions\u0027: ("},{"line_number":1105,"context_line":"            args.clobber_hardlink_collisions or"},{"line_number":1106,"context_line":"            config_true_value(conf.get(\u0027clobber_hardlink_collisions\u0027,"}],"source_content_type":"text/x-python","patch_set":22,"id":"7e7785b6_c2365a7a","line":1103,"updated":"2026-09-17 23:40:56.000000000","message":"yup, as best I can tell no `config_int_in_range` helper already exists:\n\nhttps://github.com/NVIDIA/swift/blob/master/swift/common/utils/config.py#L71\n\nthis ensures every `max_num_ppi` passed to `_is_ppi_ancestor` is always in the expected range.","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"}],"test/unit/cli/test_relinker.py":[{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":381,"context_line":"                               return_value\u003d[\u0027sda\u0027, \u0027sdb\u0027]):"},{"line_number":382,"context_line":"                mock_relinker.return_value.run.return_value \u003d 0"},{"line_number":383,"context_line":"                call_capture.attach_mock(mock_dp, \u0027drop_privileges\u0027)"},{"line_number":384,"context_line":"                call_capture.attach_mock(mock_relinker, \u0027run\u0027)"},{"line_number":385,"context_line":"                yield call_capture"},{"line_number":386,"context_line":""},{"line_number":387,"context_line":"        # no user option"}],"source_content_type":"text/x-python","patch_set":12,"id":"13a7cbdd_d1a082b0","side":"PARENT","line":384,"updated":"2026-05-06 14:47:24.000000000","message":"*maybe* it\u0027d be better if this was `__init__` - I\u0027ve never seen this `attach_mock` thing before:\n\nhttps://docs.python.org/3/library/unittest.mock.html#unittest.mock.Mock.attach_mock\n\nit\u0027s possible that\u0027s the test is trying to make assertions about the order in which `drop_privileges` and `Relinker(...).run()` are called?","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":381,"context_line":"                               return_value\u003d[\u0027sda\u0027, \u0027sdb\u0027]):"},{"line_number":382,"context_line":"                mock_relinker.return_value.run.return_value \u003d 0"},{"line_number":383,"context_line":"                call_capture.attach_mock(mock_dp, \u0027drop_privileges\u0027)"},{"line_number":384,"context_line":"                call_capture.attach_mock(mock_relinker, \u0027run\u0027)"},{"line_number":385,"context_line":"                yield call_capture"},{"line_number":386,"context_line":""},{"line_number":387,"context_line":"        # no user option"}],"source_content_type":"text/x-python","patch_set":12,"id":"06a0ff8a_7b8b9dd1","side":"PARENT","line":384,"in_reply_to":"13a7cbdd_d1a082b0","updated":"2026-05-12 17:09:08.000000000","message":"I think its about the ordering too. Will avoid a drive-by.","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":388,"context_line":"        with do_mocks() as capture:"},{"line_number":389,"context_line":"            self.assertEqual(0, relinker.main([command, \u0027--workers\u0027, \u00270\u0027]))"},{"line_number":390,"context_line":"        self.assertEqual([mock.call.run(mock.ANY, mock.ANY, [\u0027sda\u0027, \u0027sdb\u0027],"},{"line_number":391,"context_line":"                                        do_cleanup\u003d(command \u003d\u003d \u0027cleanup\u0027))],"},{"line_number":392,"context_line":"                         capture.method_calls)"},{"line_number":393,"context_line":""},{"line_number":394,"context_line":"        # cli option --user"}],"source_content_type":"text/x-python","patch_set":12,"id":"2bdf34e3_51642039","side":"PARENT","line":391,"updated":"2026-05-06 14:47:24.000000000","message":"this is a annoying change - it *reads* like a signature change (which it is!) but the pointer `mock.call.run` *seems* like it\u0027s trying to say \"the signature of run changed!\" ... but that\u0027s not it:\n\n```\n    def run(self):\n```\n\nwhat *really* changed is `__init__`:\n\n\n```\n-    def __init__(self, conf, logger, device_list\u003dNone, do_cleanup\u003dFalse):\n+    def __init__(self, conf, logger, device_list\u003dNone, step\u003dStep.RELINK):\n```\n\nThe reason `run` shows up is because of the way this `do_mocks` \"aggregates\" all of the mocks onto a single mock object - and explicitly says \"whatever the class mock_relinker is called with, name that `run`\"","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":388,"context_line":"        with do_mocks() as capture:"},{"line_number":389,"context_line":"            self.assertEqual(0, relinker.main([command, \u0027--workers\u0027, \u00270\u0027]))"},{"line_number":390,"context_line":"        self.assertEqual([mock.call.run(mock.ANY, mock.ANY, [\u0027sda\u0027, \u0027sdb\u0027],"},{"line_number":391,"context_line":"                                        do_cleanup\u003d(command \u003d\u003d \u0027cleanup\u0027))],"},{"line_number":392,"context_line":"                         capture.method_calls)"},{"line_number":393,"context_line":""},{"line_number":394,"context_line":"        # cli option --user"}],"source_content_type":"text/x-python","patch_set":12,"id":"60e02ed0_de43f5b7","side":"PARENT","line":391,"in_reply_to":"2bdf34e3_51642039","updated":"2026-05-12 17:09:08.000000000","message":"The change is restricted to the `do_cleanup` -\u003e `step` arg name. Is this a comment on the existing state of the code?","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":false,"context_lines":[{"line_number":388,"context_line":"        with do_mocks() as capture:"},{"line_number":389,"context_line":"            self.assertEqual(0, relinker.main([command, \u0027--workers\u0027, \u00270\u0027]))"},{"line_number":390,"context_line":"        self.assertEqual([mock.call.run(mock.ANY, mock.ANY, [\u0027sda\u0027, \u0027sdb\u0027],"},{"line_number":391,"context_line":"                                        do_cleanup\u003d(command \u003d\u003d \u0027cleanup\u0027))],"},{"line_number":392,"context_line":"                         capture.method_calls)"},{"line_number":393,"context_line":""},{"line_number":394,"context_line":"        # cli option --user"}],"source_content_type":"text/x-python","patch_set":12,"id":"faa7f2ff_9616279e","side":"PARENT","line":391,"in_reply_to":"60e02ed0_de43f5b7","updated":"2026-08-21 05:26:05.000000000","message":"\u003e comment on the existing state of the code?\n\nYes!  specifically:\n\n```\n    call_capture.attach_mock(mock_relinker, \u0027run\u0027)\n```\n\nis *insane* but also:\n\nN.B. `swift.cli.relinker.Relinker.__init__` signature did change, and `swift.cli.relinker.Relinker.run` did NOT","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":1000,"context_line":"                     exp_old_specs, exp_new_specs):"},{"line_number":1001,"context_line":"        # force the rehash to not happen during relink so that we can inspect"},{"line_number":1002,"context_line":"        # files in the new partition hash dir before they are cleaned up"},{"line_number":1003,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"},{"line_number":1004,"context_line":"        self.rb.prepare_increase_partition_power()"},{"line_number":1005,"context_line":"        self._save_ring()"},{"line_number":1006,"context_line":"        self._do_link_test(\u0027relink\u0027, old_file_specs, new_file_specs, None,"}],"source_content_type":"text/x-python","patch_set":12,"id":"9f1c175b_c1b0ea16","side":"PARENT","line":1003,"updated":"2026-05-06 14:47:24.000000000","message":"I think what this is saying was that `_relink_test` was only ever called w/ relink phase from PART_POWER\u003d\u003ePART_POWER+1","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":1000,"context_line":"                     exp_old_specs, exp_new_specs):"},{"line_number":1001,"context_line":"        # force the rehash to not happen during relink so that we can inspect"},{"line_number":1002,"context_line":"        # files in the new partition hash dir before they are cleaned up"},{"line_number":1003,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"},{"line_number":1004,"context_line":"        self.rb.prepare_increase_partition_power()"},{"line_number":1005,"context_line":"        self._save_ring()"},{"line_number":1006,"context_line":"        self._do_link_test(\u0027relink\u0027, old_file_specs, new_file_specs, None,"}],"source_content_type":"text/x-python","patch_set":12,"id":"00b282ff_547ebd08","side":"PARENT","line":1003,"in_reply_to":"9f1c175b_c1b0ea16","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":1001,"context_line":"        # force the rehash to not happen during relink so that we can inspect"},{"line_number":1002,"context_line":"        # files in the new partition hash dir before they are cleaned up"},{"line_number":1003,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"},{"line_number":1004,"context_line":"        self.rb.prepare_increase_partition_power()"},{"line_number":1005,"context_line":"        self._save_ring()"},{"line_number":1006,"context_line":"        self._do_link_test(\u0027relink\u0027, old_file_specs, new_file_specs, None,"},{"line_number":1007,"context_line":"                           exp_old_specs, exp_new_specs)"}],"source_content_type":"text/x-python","patch_set":12,"id":"e3383bba_11ac9bf9","side":"PARENT","line":1004,"updated":"2026-05-06 14:47:24.000000000","message":"... and in fact this helper was clearly driving you INTO relink phase, so using the ring\u0027s pre-existing PART_POWER is equivalent for this setup.","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":1001,"context_line":"        # force the rehash to not happen during relink so that we can inspect"},{"line_number":1002,"context_line":"        # files in the new partition hash dir before they are cleaned up"},{"line_number":1003,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"},{"line_number":1004,"context_line":"        self.rb.prepare_increase_partition_power()"},{"line_number":1005,"context_line":"        self._save_ring()"},{"line_number":1006,"context_line":"        self._do_link_test(\u0027relink\u0027, old_file_specs, new_file_specs, None,"},{"line_number":1007,"context_line":"                           exp_old_specs, exp_new_specs)"}],"source_content_type":"text/x-python","patch_set":12,"id":"61668d1f_3fe3282e","side":"PARENT","line":1004,"in_reply_to":"e3383bba_11ac9bf9","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":2992,"context_line":"            fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)"},{"line_number":2993,"context_line":""},{"line_number":2994,"context_line":"    def _test_state_file(self, pol, expected_recon_data):"},{"line_number":2995,"context_line":"        datadir \u003d \u0027objects\u0027"},{"line_number":2996,"context_line":"        device_path \u003d os.path.join(self.devices, self.existing_device)"},{"line_number":2997,"context_line":"        datadir_path \u003d os.path.join(device_path, datadir)"},{"line_number":2998,"context_line":"        state_file \u003d os.path.join(device_path, \u0027relink.%s.json\u0027 % datadir)"}],"source_content_type":"text/x-python","patch_set":12,"id":"c616b53f_2ad6ae13","side":"PARENT","line":2995,"updated":"2026-05-06 14:47:24.000000000","message":"existing test wonky-ness if `diskfile.get_data_dir(pol) !\u003d datadir`","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":2992,"context_line":"            fcntl.flock(f.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)"},{"line_number":2993,"context_line":""},{"line_number":2994,"context_line":"    def _test_state_file(self, pol, expected_recon_data):"},{"line_number":2995,"context_line":"        datadir \u003d \u0027objects\u0027"},{"line_number":2996,"context_line":"        device_path \u003d os.path.join(self.devices, self.existing_device)"},{"line_number":2997,"context_line":"        datadir_path \u003d os.path.join(device_path, datadir)"},{"line_number":2998,"context_line":"        state_file \u003d os.path.join(device_path, \u0027relink.%s.json\u0027 % datadir)"}],"source_content_type":"text/x-python","patch_set":12,"id":"e40d348f_929430f4","side":"PARENT","line":2995,"in_reply_to":"c616b53f_2ad6ae13","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"453e2356ba92fdefbe9e0886028d8414a1063684"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":391,"context_line":"        with do_mocks() as capture:"},{"line_number":392,"context_line":"            self.assertEqual(0, relinker.main([command, \u0027--workers\u0027, \u00270\u0027]))"},{"line_number":393,"context_line":"        self.assertEqual([mock.call.run(mock.ANY, mock.ANY, [\u0027sda\u0027, \u0027sdb\u0027],"},{"line_number":394,"context_line":"                                        step\u003dstep)],"},{"line_number":395,"context_line":"                         capture.method_calls)"},{"line_number":396,"context_line":""},{"line_number":397,"context_line":"        # cli option --user"}],"source_content_type":"text/x-python","patch_set":12,"id":"819b8794_1566accf","line":394,"updated":"2026-05-06 14:47:24.000000000","message":"so this is testing \"routing\" as well - on the CLI if you pass in `Step.value` it will get dispatched to `Relinker(..., step\u003dstep).run()`","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":391,"context_line":"        with do_mocks() as capture:"},{"line_number":392,"context_line":"            self.assertEqual(0, relinker.main([command, \u0027--workers\u0027, \u00270\u0027]))"},{"line_number":393,"context_line":"        self.assertEqual([mock.call.run(mock.ANY, mock.ANY, [\u0027sda\u0027, \u0027sdb\u0027],"},{"line_number":394,"context_line":"                                        step\u003dstep)],"},{"line_number":395,"context_line":"                         capture.method_calls)"},{"line_number":396,"context_line":""},{"line_number":397,"context_line":"        # cli option --user"}],"source_content_type":"text/x-python","patch_set":12,"id":"f6233ecf_cc1aad13","line":394,"in_reply_to":"819b8794_1566accf","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":926,"context_line":"        def make_filenames(specs):"},{"line_number":927,"context_line":"            filenames \u003d []"},{"line_number":928,"context_line":"            for ext, ts_delta in specs:"},{"line_number":929,"context_line":"                ts \u003d utils.Timestamp(float(self.obj_ts) + ts_delta)"},{"line_number":930,"context_line":"                filename \u003d \u0027.\u0027.join([ts.internal, ext])"},{"line_number":931,"context_line":"                filenames.append(filename)"},{"line_number":932,"context_line":"            return filenames"}],"source_content_type":"text/x-python","patch_set":12,"id":"26455e9b_a13a2d0e","line":929,"updated":"2026-05-06 14:47:24.000000000","message":"maybe un-related?","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":926,"context_line":"        def make_filenames(specs):"},{"line_number":927,"context_line":"            filenames \u003d []"},{"line_number":928,"context_line":"            for ext, ts_delta in specs:"},{"line_number":929,"context_line":"                ts \u003d utils.Timestamp(float(self.obj_ts) + ts_delta)"},{"line_number":930,"context_line":"                filename \u003d \u0027.\u0027.join([ts.internal, ext])"},{"line_number":931,"context_line":"                filenames.append(filename)"},{"line_number":932,"context_line":"            return filenames"}],"source_content_type":"text/x-python","patch_set":12,"id":"9b29c22f_45a17e09","line":929,"in_reply_to":"26455e9b_a13a2d0e","updated":"2026-05-12 17:09:08.000000000","message":"Indeed. Reverted. Thanks.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":1003,"context_line":"                     exp_old_specs, exp_new_specs):"},{"line_number":1004,"context_line":"        # force the rehash to not happen during relink so that we can inspect"},{"line_number":1005,"context_line":"        # files in the new partition hash dir before they are cleaned up"},{"line_number":1006,"context_line":"        part_power \u003d self.rb.part_power"},{"line_number":1007,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (part_power - 1))"},{"line_number":1008,"context_line":"        self.rb.prepare_increase_partition_power()"},{"line_number":1009,"context_line":"        self._save_ring()"}],"source_content_type":"text/x-python","patch_set":12,"id":"f7fa1a37_31c968b9","line":1006,"updated":"2026-05-06 14:47:24.000000000","message":"this helper becomes more flexible - seems reasonable.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":1003,"context_line":"                     exp_old_specs, exp_new_specs):"},{"line_number":1004,"context_line":"        # force the rehash to not happen during relink so that we can inspect"},{"line_number":1005,"context_line":"        # files in the new partition hash dir before they are cleaned up"},{"line_number":1006,"context_line":"        part_power \u003d self.rb.part_power"},{"line_number":1007,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (part_power - 1))"},{"line_number":1008,"context_line":"        self.rb.prepare_increase_partition_power()"},{"line_number":1009,"context_line":"        self._save_ring()"}],"source_content_type":"text/x-python","patch_set":12,"id":"edb0f775_835dae43","line":1006,"in_reply_to":"f7fa1a37_31c968b9","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":3015,"context_line":"            get_prefixed_swift_logger(self.logger,"},{"line_number":3016,"context_line":"                                      f\u0027[step\u003d{step.value}] \u0027),"},{"line_number":3017,"context_line":"            [self.existing_device], step)"},{"line_number":3018,"context_line":"        r.datadir \u003d datadir"},{"line_number":3019,"context_line":"        r.part_power \u003d PART_POWER + 1 if step \u003d\u003d relinker.Step.CLEANUP \\"},{"line_number":3020,"context_line":"            else PART_POWER"},{"line_number":3021,"context_line":"        r.next_part_power \u003d None if step \u003d\u003d relinker.Step.AUDIT \\"}],"source_content_type":"text/x-python","patch_set":12,"id":"69938dc7_f2bfdad9","line":3018,"updated":"2026-05-06 14:47:24.000000000","message":"how is `r.datadata \u003d None` *ever* ok!?\n\nwell it\u0027s fine if you don\u0027t use any of the audit_location_generator -  `self.datadir` is used by the pre device hook, and notably statefile tracking.\n\nIf you are calling into basically any other relinker method - it doesn\u0027t care about this attribute\u0027s value\n\nIt\u0027s typically initialized to:\n\n```\n        self.datadir \u003d diskfile.get_data_dir(policy)\n```\n\n... and honestly that might be a good case for it not even be *possible* for this helper to initialize it to a inconsistent value.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":3015,"context_line":"            get_prefixed_swift_logger(self.logger,"},{"line_number":3016,"context_line":"                                      f\u0027[step\u003d{step.value}] \u0027),"},{"line_number":3017,"context_line":"            [self.existing_device], step)"},{"line_number":3018,"context_line":"        r.datadir \u003d datadir"},{"line_number":3019,"context_line":"        r.part_power \u003d PART_POWER + 1 if step \u003d\u003d relinker.Step.CLEANUP \\"},{"line_number":3020,"context_line":"            else PART_POWER"},{"line_number":3021,"context_line":"        r.next_part_power \u003d None if step \u003d\u003d relinker.Step.AUDIT \\"}],"source_content_type":"text/x-python","patch_set":12,"id":"a1127958_e83460a1","line":3018,"in_reply_to":"69938dc7_f2bfdad9","updated":"2026-05-12 17:09:08.000000000","message":"I\u0027ve dropped the `datadir` parameter from this method\u0027s signature. It\u0027s up to consumers of the returned `Relinker` instance to override the `datadir` field.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":3019,"context_line":"        r.part_power \u003d PART_POWER + 1 if step \u003d\u003d relinker.Step.CLEANUP \\"},{"line_number":3020,"context_line":"            else PART_POWER"},{"line_number":3021,"context_line":"        r.next_part_power \u003d None if step \u003d\u003d relinker.Step.AUDIT \\"},{"line_number":3022,"context_line":"            else PART_POWER + 1"},{"line_number":3023,"context_line":"        r.policy \u003d pol"},{"line_number":3024,"context_line":"        r.pid \u003d 1234  # for recon workers stats"},{"line_number":3025,"context_line":"        r.diskfile_mgr \u003d DiskFileRouter({"}],"source_content_type":"text/x-python","patch_set":12,"id":"db2ebc2b_4d6646d7","line":3022,"updated":"2026-05-06 14:47:24.000000000","message":"i like it; `next_part_power` becomes \"dynamic\" in the same way that `part_power` was already dynamic based on the `step` argument passed in - so the caller can say say what `step` they want and the helper sets up a realistic fake ring.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":3019,"context_line":"        r.part_power \u003d PART_POWER + 1 if step \u003d\u003d relinker.Step.CLEANUP \\"},{"line_number":3020,"context_line":"            else PART_POWER"},{"line_number":3021,"context_line":"        r.next_part_power \u003d None if step \u003d\u003d relinker.Step.AUDIT \\"},{"line_number":3022,"context_line":"            else PART_POWER + 1"},{"line_number":3023,"context_line":"        r.policy \u003d pol"},{"line_number":3024,"context_line":"        r.pid \u003d 1234  # for recon workers stats"},{"line_number":3025,"context_line":"        r.diskfile_mgr \u003d DiskFileRouter({"}],"source_content_type":"text/x-python","patch_set":12,"id":"26fc84fb_30dc7dfc","line":3022,"in_reply_to":"db2ebc2b_4d6646d7","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4243,"context_line":"    def test_is_ppi_ancestor_partition_zero(self):"},{"line_number":4244,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(0, 255))"},{"line_number":4245,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(0, 1, max_num_ppi\u003d1))"},{"line_number":4246,"context_line":"        self.assertFalse(relinker._is_ppi_ancestor(0, 2, max_num_ppi\u003d1))"},{"line_number":4247,"context_line":""},{"line_number":4248,"context_line":"    def test_is_ppi_ancestor_invalid(self):"},{"line_number":4249,"context_line":"        with self.assertRaises(ValueError):"}],"source_content_type":"text/x-python","patch_set":12,"id":"e1f3abbb_e652fbc3","line":4246,"updated":"2026-05-06 14:47:24.000000000","message":"I might enjoy\n\n(0, 0), (0, 1)\n(1, 2), (1, 3)\n\njust to really drive home that (2n, 2n+1) mapping/decensdent","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":4243,"context_line":"    def test_is_ppi_ancestor_partition_zero(self):"},{"line_number":4244,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(0, 255))"},{"line_number":4245,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(0, 1, max_num_ppi\u003d1))"},{"line_number":4246,"context_line":"        self.assertFalse(relinker._is_ppi_ancestor(0, 2, max_num_ppi\u003d1))"},{"line_number":4247,"context_line":""},{"line_number":4248,"context_line":"    def test_is_ppi_ancestor_invalid(self):"},{"line_number":4249,"context_line":"        with self.assertRaises(ValueError):"}],"source_content_type":"text/x-python","patch_set":12,"id":"fbbe0df4_aa59e932","line":4246,"in_reply_to":"e1f3abbb_e652fbc3","updated":"2026-05-12 17:09:08.000000000","message":"Should I read each tuple as `(n, 2n[+1])`? If so, then `(1, 2)` and `(2, 3)` don\u0027t belong in this test case. The closest test case would be `test_is_ppi_ancestor_direct`.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":false,"context_lines":[{"line_number":4243,"context_line":"    def test_is_ppi_ancestor_partition_zero(self):"},{"line_number":4244,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(0, 255))"},{"line_number":4245,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(0, 1, max_num_ppi\u003d1))"},{"line_number":4246,"context_line":"        self.assertFalse(relinker._is_ppi_ancestor(0, 2, max_num_ppi\u003d1))"},{"line_number":4247,"context_line":""},{"line_number":4248,"context_line":"    def test_is_ppi_ancestor_invalid(self):"},{"line_number":4249,"context_line":"        with self.assertRaises(ValueError):"}],"source_content_type":"text/x-python","patch_set":12,"id":"3d087fc3_5bfc407e","line":4246,"in_reply_to":"fbbe0df4_aa59e932","updated":"2026-09-17 23:40:56.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4269,"context_line":"            r.audit_location(self.existing_device, hp)"},{"line_number":4270,"context_line":""},{"line_number":4271,"context_line":"        qr.assert_not_called()"},{"line_number":4272,"context_line":"        self.assertEqual(r.stats[\u0027hash_dirs\u0027], 0)"},{"line_number":4273,"context_line":"        self.assertEqual(r.stats[\u0027removed\u0027], 0)"},{"line_number":4274,"context_line":"        self.assertEqual(r.stats[\u0027errors\u0027], 0)"},{"line_number":4275,"context_line":"        self.assertEqual(r.stats[\u0027warnings\u0027], 0)"}],"source_content_type":"text/x-python","patch_set":12,"id":"6d77cf82_8b8c1d8d","line":4272,"updated":"2026-05-06 14:47:24.000000000","message":"maybe worth a comment for `# matching hash_path isn\u0027t counted` (???)\n\n... with some sort of justification on \"b/c that\u0027s what process_location did\"","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":4269,"context_line":"            r.audit_location(self.existing_device, hp)"},{"line_number":4270,"context_line":""},{"line_number":4271,"context_line":"        qr.assert_not_called()"},{"line_number":4272,"context_line":"        self.assertEqual(r.stats[\u0027hash_dirs\u0027], 0)"},{"line_number":4273,"context_line":"        self.assertEqual(r.stats[\u0027removed\u0027], 0)"},{"line_number":4274,"context_line":"        self.assertEqual(r.stats[\u0027errors\u0027], 0)"},{"line_number":4275,"context_line":"        self.assertEqual(r.stats[\u0027warnings\u0027], 0)"}],"source_content_type":"text/x-python","patch_set":12,"id":"c9cfbb55_fcf9ee1b","line":4272,"in_reply_to":"6d77cf82_8b8c1d8d","updated":"2026-05-12 17:09:08.000000000","message":"Done","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4321,"context_line":"            r.audit_location(self.existing_device, hp_correct)"},{"line_number":4322,"context_line":"            r.audit_location(self.existing_device, hp_ancestor)"},{"line_number":4323,"context_line":""},{"line_number":4324,"context_line":"        self.assertEqual(r.stats[\u0027hash_dirs\u0027], 1)"},{"line_number":4325,"context_line":"        self.assertEqual(r.stats[\u0027removed\u0027], 1)"},{"line_number":4326,"context_line":"        self.assertEqual(r.stats[\u0027errors\u0027], 0)"},{"line_number":4327,"context_line":"        self.assertEqual(r.stats[\u0027warnings\u0027], 0)"}],"source_content_type":"text/x-python","patch_set":12,"id":"5ee97ad3_3cd6188b","line":4324,"updated":"2026-05-06 14:47:24.000000000","message":"I was expecting this to be \"2\" \n\nthe reason it\u0027s confusing is because `process_location` *always* increments hash_path (but the caller *filters* calls based on matching paths)\n\nwere as `audit_location` is *always* called (no filter) - but sometimes \"skips\" matching hash_paths\n\n... I think this is a weird combination of \"consistent but not\"\n\n1) `process_location` is weird, it\u0027s essentially \"always relink maybe cleanup\"\n2) it should do the right thing even if we don\u0027t pre-filter during cleanup\n3) I think it\u0027s an omission that we don\u0027t count skipped and filtered hashes\n\n^ i\u0027m not sure what that says about the ideal amount of code to have as part of this change.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":4321,"context_line":"            r.audit_location(self.existing_device, hp_correct)"},{"line_number":4322,"context_line":"            r.audit_location(self.existing_device, hp_ancestor)"},{"line_number":4323,"context_line":""},{"line_number":4324,"context_line":"        self.assertEqual(r.stats[\u0027hash_dirs\u0027], 1)"},{"line_number":4325,"context_line":"        self.assertEqual(r.stats[\u0027removed\u0027], 1)"},{"line_number":4326,"context_line":"        self.assertEqual(r.stats[\u0027errors\u0027], 0)"},{"line_number":4327,"context_line":"        self.assertEqual(r.stats[\u0027warnings\u0027], 0)"}],"source_content_type":"text/x-python","patch_set":12,"id":"9082da17_e98239e9","line":4324,"in_reply_to":"5ee97ad3_3cd6188b","updated":"2026-05-12 17:09:08.000000000","message":"I agree that\u0027s it\u0027s confusing. I think a reasonable followup change accomplishes:\n- `process_location` conditionally filter out calls like `audit location`.\n- Fix hash accounting for both functions.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":4321,"context_line":"            r.audit_location(self.existing_device, hp_correct)"},{"line_number":4322,"context_line":"            r.audit_location(self.existing_device, hp_ancestor)"},{"line_number":4323,"context_line":""},{"line_number":4324,"context_line":"        self.assertEqual(r.stats[\u0027hash_dirs\u0027], 1)"},{"line_number":4325,"context_line":"        self.assertEqual(r.stats[\u0027removed\u0027], 1)"},{"line_number":4326,"context_line":"        self.assertEqual(r.stats[\u0027errors\u0027], 0)"},{"line_number":4327,"context_line":"        self.assertEqual(r.stats[\u0027warnings\u0027], 0)"}],"source_content_type":"text/x-python","patch_set":12,"id":"d5496655_664f601d","line":4324,"in_reply_to":"9082da17_e98239e9","updated":"2026-08-21 05:26:05.000000000","message":"both `audit_location` and `process_location` are subject to `hashes_filter` - that\u0027s not what this comment is about, in this test `audit_location` is called directly\n\nboth `audit_location` and `process_location` increment `self.stats[\u0027hash_dirs\u0027] +\u003d 1` the difference is in their conditioning of that increment\n\nIn `process_policy` the \"router\" decides:\n\n```\n        for hash_path, device, _ in locations:\n            if self.step \u003d\u003d Step.AUDIT:\n                self.audit_location(device, hash_path)\n            else:\n                new_hash_path \u003d replace_partition_in_path(\n                    self.conf[\u0027devices\u0027], hash_path, self.next_part_power)\n                if new_hash_path \u003d\u003d hash_path:\n                    continue\n                self.process_location(device, hash_path, new_hash_path)\n\n```\n\nso even tho you wouldn\u0027t normally expect locations to yield unless it\u0027s \"mismatched_hashes\" - the difference in responsibility for where the noop guard gets implemented pushes `audit_location` into the confusing accounting in order to maintain consistenty with process_location - hence \"consistent but not\"\n\ni.e. the responsibility is *inconsistent* on purpose, because `audit_location` wants to calculate `expected_hash_path` instead of have it passed in and b/c of that design choice the accounting MUST be non-obvious IF you want the accounting to be *consistent* with process_location.\n\nMy suggestion is this:\n\n```\n(vagrant-swift-all-in-one) cgerrard@NVStation:~/Workspace/vagrant-swift-all-in-one/swift$ git diff\ndiff --git a/swift/cli/relinker.py b/swift/cli/relinker.py\nindex af0a687ce..927de7876 100644\n--- a/swift/cli/relinker.py\n+++ b/swift/cli/relinker.py\n@@ -562,8 +562,6 @@ class Relinker(object):\n         if expected_hash_path \u003d\u003d hash_path:\n             return\n \n-        self.stats[\u0027hash_dirs\u0027] +\u003d 1\n-\n         expected_part \u003d get_partition_from_path(self.conf[\u0027devices\u0027],\n                                                 expected_hash_path)\n         found_part \u003d get_partition_from_path(self.conf[\u0027devices\u0027], hash_path)\n@@ -607,8 +605,6 @@ class Relinker(object):\n         :param hash_path: old hash directory path\n         :param new_hash_path: new hash directory path\n         \"\"\"\n-        self.stats[\u0027hash_dirs\u0027] +\u003d 1\n-\n         # Get on disk data for new and old locations, cleaning up any\n         # reclaimable or obsolete files in each. The new location is\n         # cleaned up *before* the old location to prevent false negatives\n@@ -752,6 +748,7 @@ class Relinker(object):\n             locations \u003d RateLimitedIterator(\n                 locations, self.conf[\u0027files_per_second\u0027])\n         for hash_path, device, _ in locations:\n+            self.stats[\u0027hash_dirs\u0027] +\u003d 1\n             if self.step \u003d\u003d Step.AUDIT:\n                 self.audit_location(device, hash_path)\n             else:\n```\n\ni.e. fix process_location accounting in order to avoid making audit_location look stupid\n\nyes, it changes the existing counting of hash_dirs for process_location - but it does it in a way that\u0027s useful and obvious!  The benifit of this suggestion over what\u0027s written is it avoids introducing confusing accounting to the *new* `audit_location` \"just\" to maintain akward consistenty with the questionable pre-eixsting behavior of the non-conforming `process_location`!\n\nI would also accept \"any changes to process_location should be out of scope\" - but I think the compromise should be that `audit_location` boldly assert it truely virtous to \"bury the no op IN the method\" proudly by counting `hash_dirs` before the guard return\n\n```\n(vagrant-swift-all-in-one) cgerrard@NVStation:~/Workspace/vagrant-swift-all-in-one/swift$ git diff\ndiff --git a/swift/cli/relinker.py b/swift/cli/relinker.py\nindex af0a687ce..adf93d165 100644\n--- a/swift/cli/relinker.py\n+++ b/swift/cli/relinker.py\n@@ -557,13 +557,13 @@ class Relinker(object):\n           Left as is.\n         - Hash is in the expected partition: No action needed.\n         \"\"\"\n+        self.stats[\u0027hash_dirs\u0027] +\u003d 1\n+\n         expected_hash_path \u003d replace_partition_in_path(\n             self.conf[\u0027devices\u0027], hash_path, self.part_power)\n         if expected_hash_path \u003d\u003d hash_path:\n             return\n \n-        self.stats[\u0027hash_dirs\u0027] +\u003d 1\n-\n         expected_part \u003d get_partition_from_path(self.conf[\u0027devices\u0027],\n                                                 expected_hash_path)\n         found_part \u003d get_partition_from_path(self.conf[\u0027devices\u0027], hash_path)\n```\n\n^ this would, as a side-effect, make it LOOK like `audit_location` unconditionally countomg each hash for which it\u0027s called is \"the same\" as `process_location` (ignoring that want to bound in and out of stack to achive the same no op)\n\n\u003e process_location conditionally filter out calls like audit location\n\nN.B. if we eventually \"fix\" `process_location` such that `new_hash_path` is derivded-in-method instead of passed-in-as-argument so it\u0027s signature will mirror `audit_location(device, hash_path)` we\u0027ll end up with the same `if expected_hash_path \u003d\u003d hash_path:` \"guard return\" at the top of both methods - which again would get simpler w/ a `self.expected_policy` property","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":4321,"context_line":"            r.audit_location(self.existing_device, hp_correct)"},{"line_number":4322,"context_line":"            r.audit_location(self.existing_device, hp_ancestor)"},{"line_number":4323,"context_line":""},{"line_number":4324,"context_line":"        self.assertEqual(r.stats[\u0027hash_dirs\u0027], 1)"},{"line_number":4325,"context_line":"        self.assertEqual(r.stats[\u0027removed\u0027], 1)"},{"line_number":4326,"context_line":"        self.assertEqual(r.stats[\u0027errors\u0027], 0)"},{"line_number":4327,"context_line":"        self.assertEqual(r.stats[\u0027warnings\u0027], 0)"}],"source_content_type":"text/x-python","patch_set":12,"id":"8b6e7a28_80032408","line":4324,"in_reply_to":"d5496655_664f601d","updated":"2026-09-17 04:22:20.000000000","message":"So overall accounting is keep close to the loop, as opposed in the `(audit/process)_location` methods? I like it.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4326,"context_line":"        self.assertEqual(r.stats[\u0027errors\u0027], 0)"},{"line_number":4327,"context_line":"        self.assertEqual(r.stats[\u0027warnings\u0027], 0)"},{"line_number":4328,"context_line":""},{"line_number":4329,"context_line":"    def test_audit_location_quarantine_renamer_raises(self):"},{"line_number":4330,"context_line":"        r \u003d self._create_relinker(relinker.Step.AUDIT)"},{"line_number":4331,"context_line":"        hsh \u003d utils.hash_path(\u0027a\u0027, \u0027c\u0027, \u0027o\u0027)"},{"line_number":4332,"context_line":"        expected_part \u003d utils.get_partition_for_hash(hsh, PART_POWER)"}],"source_content_type":"text/x-python","patch_set":12,"id":"7a95a57e_8c90a7e1","line":4329,"updated":"2026-05-06 14:47:24.000000000","message":"... when I see `test_\u003csomething\u003e_raises` I\u0027m looking for a `self.assertRaises`\n\nmaybe rename this:  `test_audit_location_ancestor_quarantine_error`\n\nwhen I set `test_\u003csomething\u003e_error` I\u0027m typically looking for \"setup some kind of error - and assert we do the right thing\" (which is often: log it but don\u0027t blow up) ... this test is setting up a \"ancestor quarantine\" that *blows up* and the \"correct behavior\" it calls out is 1) don\u0027t blow up 2) increment error\n\n... presumably there\u0027s also something interesting in `self.logger.get_lines_for_level(\u0027error\u0027)`\n\n```\n(Pdb) !self.logger.get_lines_for_level(\u0027error\u0027)\n[\u0027[step\u003daudit] Could not quarantine hash_path\u003d/mnt/tmp/tmp6lcnfn9d/node/sda1/objects/48/609/609099584d4a31526e840b664cb0dbbe: : \u0027]\n(Pdb) !self.logger.log_dict\ndefaultdict(\u003cclass \u0027list\u0027\u003e, {\u0027error\u0027: [((\u0027[step\u003daudit] Could not quarantine hash_path\u003d/mnt/tmp/tmp6lcnfn9d/node/sda1/objects/48/609/609099584d4a31526e840b664cb0dbbe: : \u0027,), {\u0027exc_info\u0027: (\u003cclass \u0027OSError\u0027\u003e, OSError(), \u003ctraceback object at 0x7fd83d0623c0\u003e), \u0027extra\u0027: {\u0027server\u0027: \u0027test\u0027, \u0027txn_id\u0027: None, \u0027client_ip\u0027: None}})]})\n```","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":4326,"context_line":"        self.assertEqual(r.stats[\u0027errors\u0027], 0)"},{"line_number":4327,"context_line":"        self.assertEqual(r.stats[\u0027warnings\u0027], 0)"},{"line_number":4328,"context_line":""},{"line_number":4329,"context_line":"    def test_audit_location_quarantine_renamer_raises(self):"},{"line_number":4330,"context_line":"        r \u003d self._create_relinker(relinker.Step.AUDIT)"},{"line_number":4331,"context_line":"        hsh \u003d utils.hash_path(\u0027a\u0027, \u0027c\u0027, \u0027o\u0027)"},{"line_number":4332,"context_line":"        expected_part \u003d utils.get_partition_for_hash(hsh, PART_POWER)"}],"source_content_type":"text/x-python","patch_set":12,"id":"69bd6aaf_b08170f7","line":4329,"in_reply_to":"7a95a57e_8c90a7e1","updated":"2026-05-12 17:09:08.000000000","message":"Addressed. Thanks.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4361,"context_line":"        r.conf[\u0027policies\u0027] \u003d POLICIES"},{"line_number":4362,"context_line":"        r.conf[\u0027swift_dir\u0027] \u003d self.testdir"},{"line_number":4363,"context_line":""},{"line_number":4364,"context_line":"        self.assertEqual(r.run(), relinker.EXIT_NO_APPLICABLE_POLICY)"},{"line_number":4365,"context_line":""},{"line_number":4366,"context_line":"    def test_audit_run_hash_ok(self):"},{"line_number":4367,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"}],"source_content_type":"text/x-python","patch_set":12,"id":"712c8c5c_07d86190","line":4364,"updated":"2026-05-06 14:47:24.000000000","message":"I really like how terse these new `test_\u003cphase\u003e_blocked_\u003cring_state` tests are - it would be nice to extend them to cover other conditions.\n\nI think the way this test is setup it can only be a negative test i.e. `run() \u003d\u003e EXIT_NO_APPLICABLE_POLICY` the \"positive\"/happy-path side would have to show some work of some kind and mostly we do that with `relinker.main()`\n\nmaybe these would look better unified with other existing tests:\n\n```\n    def test_relink_no_applicable_policy(self):\n        # NB do not prepare part power increase\n        self._save_ring()\n        with self._mock_relinker():\n            self.assertEqual(2, relinker.main([\n                \u0027relink\u0027,\n                \u0027--swift-dir\u0027, self.testdir,\n                \u0027--devices\u0027, self.devices,\n            ]))\n        self.assertEqual(\n            self.logger.get_lines_for_level(\u0027warning\u0027),\n            [\u0027[step\u003drelink] policy\u003dplatinum: Skipping step - \u0027 +\n             \u0027next_part_power !\u003d part_power + 1 \u0027 +\n             \u0027part_power\u003d8 next_part_power\u003dNone\u0027,\n             \u0027[step\u003drelink] No policy found to increase the partition power.\u0027])\n        self.assertEqual([], self.logger.get_lines_for_level(\u0027error\u0027))\n```","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":false,"context_lines":[{"line_number":4361,"context_line":"        r.conf[\u0027policies\u0027] \u003d POLICIES"},{"line_number":4362,"context_line":"        r.conf[\u0027swift_dir\u0027] \u003d self.testdir"},{"line_number":4363,"context_line":""},{"line_number":4364,"context_line":"        self.assertEqual(r.run(), relinker.EXIT_NO_APPLICABLE_POLICY)"},{"line_number":4365,"context_line":""},{"line_number":4366,"context_line":"    def test_audit_run_hash_ok(self):"},{"line_number":4367,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"}],"source_content_type":"text/x-python","patch_set":12,"id":"cd3929f2_d07b4075","line":4364,"in_reply_to":"4dc6bda5_ebfb9505","updated":"2026-09-17 23:40:56.000000000","message":"i think they\u0027re great - kudos!","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":4361,"context_line":"        r.conf[\u0027policies\u0027] \u003d POLICIES"},{"line_number":4362,"context_line":"        r.conf[\u0027swift_dir\u0027] \u003d self.testdir"},{"line_number":4363,"context_line":""},{"line_number":4364,"context_line":"        self.assertEqual(r.run(), relinker.EXIT_NO_APPLICABLE_POLICY)"},{"line_number":4365,"context_line":""},{"line_number":4366,"context_line":"    def test_audit_run_hash_ok(self):"},{"line_number":4367,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"}],"source_content_type":"text/x-python","patch_set":12,"id":"f2aca056_14e78198","line":4364,"in_reply_to":"712c8c5c_07d86190","updated":"2026-05-12 17:09:08.000000000","message":"Are you suggesting that I move these tests closer to the test_\u003cphase\u003e_no_applicable_policy tests, or that I rewrite them in that style, or both?","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":true,"context_lines":[{"line_number":4361,"context_line":"        r.conf[\u0027policies\u0027] \u003d POLICIES"},{"line_number":4362,"context_line":"        r.conf[\u0027swift_dir\u0027] \u003d self.testdir"},{"line_number":4363,"context_line":""},{"line_number":4364,"context_line":"        self.assertEqual(r.run(), relinker.EXIT_NO_APPLICABLE_POLICY)"},{"line_number":4365,"context_line":""},{"line_number":4366,"context_line":"    def test_audit_run_hash_ok(self):"},{"line_number":4367,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"}],"source_content_type":"text/x-python","patch_set":12,"id":"4dc6bda5_ebfb9505","line":4364,"in_reply_to":"803c0fe1_c0f8d6ce","updated":"2026-09-17 04:22:20.000000000","message":"Very helpful. Let me know how you like the new test/ordering.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":4361,"context_line":"        r.conf[\u0027policies\u0027] \u003d POLICIES"},{"line_number":4362,"context_line":"        r.conf[\u0027swift_dir\u0027] \u003d self.testdir"},{"line_number":4363,"context_line":""},{"line_number":4364,"context_line":"        self.assertEqual(r.run(), relinker.EXIT_NO_APPLICABLE_POLICY)"},{"line_number":4365,"context_line":""},{"line_number":4366,"context_line":"    def test_audit_run_hash_ok(self):"},{"line_number":4367,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"}],"source_content_type":"text/x-python","patch_set":12,"id":"803c0fe1_c0f8d6ce","line":4364,"in_reply_to":"f2aca056_14e78198","updated":"2026-08-21 05:26:05.000000000","message":"probably ~both\n\nhaving all the blocked/no_applicable_policy tests in one place aids in visual auditing that we exahustively enumerate all cases.\n\nThe benifit that jumps out of me from the existing tests are the assertions around the log lines; OTOH I think `relinker.EXIT_NO_APPLICABLE_POLICY` is significantly better than just `2` - so I wouldn\u0027t *blindly* copy the existing style just for \"foolish consistency\" and all that.\n\nThe compromise might be at least enforce similar naming to help with grep-ability b/c I don\u0027t find the \"or\" syntax of `pytest -k` super obvious:\n\n```\n$ pytest test/unit/cli/test_relinker.py -k blocked -k no_applicable | grep test/unit | grep -v Skipping\ntest/unit/cli/test_relinker.py::TestRelinker::test_cleanup_no_applicable_policy SKIPPED [ 50%]\ntest/unit/cli/test_relinker.py::TestRelinker::test_relink_no_applicable_policy SKIPPED [100%]\n$ pytest test/unit/cli/test_relinker.py -k no_applicable -k blocked | grep test/unit | grep -v Skipping\ntest/unit/cli/test_relinker.py::TestRelinker::test_audit_blocked_increase_partition_power SKIPPED [ 50%]\ntest/unit/cli/test_relinker.py::TestRelinker::test_audit_blocked_prepare_increase_partition_power SKIPPED [100%]\n$ pytest test/unit/cli/test_relinker.py -k \u0027no_applicable or blocked\u0027 | grep test/unit | grep -v Skipping\ntest/unit/cli/test_relinker.py::TestRelinker::test_audit_blocked_increase_partition_power SKIPPED [ 25%]\ntest/unit/cli/test_relinker.py::TestRelinker::test_audit_blocked_prepare_increase_partition_power SKIPPED [ 50%]\ntest/unit/cli/test_relinker.py::TestRelinker::test_cleanup_no_applicable_policy SKIPPED [ 75%]\ntest/unit/cli/test_relinker.py::TestRelinker::test_relink_no_applicable_policy SKIPPED [100%]\n```","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4364,"context_line":"        self.assertEqual(r.run(), relinker.EXIT_NO_APPLICABLE_POLICY)"},{"line_number":4365,"context_line":""},{"line_number":4366,"context_line":"    def test_audit_run_hash_ok(self):"},{"line_number":4367,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"},{"line_number":4368,"context_line":"        self._save_ring()"},{"line_number":4369,"context_line":""},{"line_number":4370,"context_line":"        with self._mock_relinker():"}],"source_content_type":"text/x-python","patch_set":12,"id":"2abd085c_13bad024","line":4367,"updated":"2026-05-06 14:47:24.000000000","message":"if I try to remove this condition:\n\n```\n-        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))\n+        self._setup_object()\n```\n\nthe test fails like:\n\n```\n\u003e       with open(state_file, \u0027rt\u0027) as f:\nE       FileNotFoundError: [Errno 2] No such file or directory: \u0027/mnt/tmp/tmp894czd2l/node/sda1/relink.objects.json\u0027\n```\n\n^ but only *sometimes*\n\nthe \"reason\" is b/c audit does \"nothing\" if the hash is in the \"uppper\" part of the hash space.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":4364,"context_line":"        self.assertEqual(r.run(), relinker.EXIT_NO_APPLICABLE_POLICY)"},{"line_number":4365,"context_line":""},{"line_number":4366,"context_line":"    def test_audit_run_hash_ok(self):"},{"line_number":4367,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"},{"line_number":4368,"context_line":"        self._save_ring()"},{"line_number":4369,"context_line":""},{"line_number":4370,"context_line":"        with self._mock_relinker():"}],"source_content_type":"text/x-python","patch_set":12,"id":"8f557442_31616ac5","line":4367,"in_reply_to":"2abd085c_13bad024","updated":"2026-05-12 17:09:08.000000000","message":"Right. An improvement would be to always create a state file regardless of whether audit does work or not?","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":4364,"context_line":"        self.assertEqual(r.run(), relinker.EXIT_NO_APPLICABLE_POLICY)"},{"line_number":4365,"context_line":""},{"line_number":4366,"context_line":"    def test_audit_run_hash_ok(self):"},{"line_number":4367,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"},{"line_number":4368,"context_line":"        self._save_ring()"},{"line_number":4369,"context_line":""},{"line_number":4370,"context_line":"        with self._mock_relinker():"}],"source_content_type":"text/x-python","patch_set":12,"id":"a5f6ec68_acc5fd2d","line":4367,"in_reply_to":"51e78e89_61652621","updated":"2026-09-17 04:22:20.000000000","message":"Thanks, I see it now.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":4364,"context_line":"        self.assertEqual(r.run(), relinker.EXIT_NO_APPLICABLE_POLICY)"},{"line_number":4365,"context_line":""},{"line_number":4366,"context_line":"    def test_audit_run_hash_ok(self):"},{"line_number":4367,"context_line":"        self._setup_object(lambda part: part \u003c 2 ** (PART_POWER - 1))"},{"line_number":4368,"context_line":"        self._save_ring()"},{"line_number":4369,"context_line":""},{"line_number":4370,"context_line":"        with self._mock_relinker():"}],"source_content_type":"text/x-python","patch_set":12,"id":"51e78e89_61652621","line":4367,"in_reply_to":"8f557442_31616ac5","updated":"2026-08-21 05:26:05.000000000","message":"I think maybe the BEST improvement would be for the test to fail even BEFORE it even tries to open the state file?\n\nUnfortunately the only other behavioral assertion seems to be on the recon/log telemetry ... the problem there comes from the telemetry for \"processed hash and did nothing\" vs \"didn\u0027t *see* the ok hash b/c it was in the upper namespace\" look exactly the same!","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4450,"context_line":""},{"line_number":4451,"context_line":"        # Ensures that placing `hash_fgn` in `part_qr` indeed"},{"line_number":4452,"context_line":"        # makes it foreign"},{"line_number":4453,"context_line":"        next_part_fgn \u003d utils.get_partition_for_hash(hash_fgn, PART_POWER + 1)"},{"line_number":4454,"context_line":"        self.assertFalse(relinker._is_ppi_ancestor(part_qr,"},{"line_number":4455,"context_line":"                                                   next_part_fgn,"},{"line_number":4456,"context_line":"                                                   max_num_ppi\u003d1))"}],"source_content_type":"text/x-python","patch_set":12,"id":"87866afd_172c4432","line":4453,"updated":"2026-05-06 14:47:24.000000000","message":"For me, with this diff the tests still pass:\n\n```\n-        next_part_fgn \u003d utils.get_partition_for_hash(hash_fgn, PART_POWER + 1)\n+        next_part_fgn \u003d utils.get_partition_for_hash(hash_fgn, PART_POWER)\n```\n\nThe point is the hash for `o2` belongs *somewhere else* i.e. NOT in the part for the hash of `o1`\n\n```\n(Pdb) !part_qr\n103\n(Pdb) !next_part_fgn\n158\n```\n\nI think having `part + 1` is \"also correct\" but having it more closely \"mirror\" the previous setup for `part_qr` might drive home the real magic comes next:\n\n```\nself._create_object(policy, part_qr, hash_fgn)\n```\n\nthis puts `hash_fgn` into `part_qr` which could *never* happen - that hash should be in `part_fgn` (or `next_part_fgn`)\n\n\nConsider maybe:\n\n```\n        # Object that must be quarantined\n        hash_qr \u003d utils.hash_path(\u0027a\u0027, \u0027c\u0027, \u0027o1\u0027)\n        part_qr \u003d utils.get_partition_for_hash(hash_qr, PART_POWER)\n        _, fname_qr, _ \u003d self._create_object(\n            policy, part_qr, hash_qr)\n\n        # foreign object that must remain in place\n        hash_fgn \u003d utils.hash_path(\u0027a\u0027, \u0027c\u0027, \u0027o2\u0027)\n        part_fgn \u003d utils.get_partition_for_hash(hash_fgn, PART_POWER)\n        # it\u0027s \"foreign\" becase we put it in o1\u0027s part\n        objdir_fgn, fname_fgn, _ \u003d self._create_object(\n            policy, part_qr, hash_fgn)\n\n        # sanity: placing `hash_fgn` in `part_qr` makes it \"foreign\"\n        self.assertNotEqual(part_qr, part_fgn)\n        self.assertFalse(relinker._is_ppi_ancestor(part_qr, part_fgn))\n```","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":4450,"context_line":""},{"line_number":4451,"context_line":"        # Ensures that placing `hash_fgn` in `part_qr` indeed"},{"line_number":4452,"context_line":"        # makes it foreign"},{"line_number":4453,"context_line":"        next_part_fgn \u003d utils.get_partition_for_hash(hash_fgn, PART_POWER + 1)"},{"line_number":4454,"context_line":"        self.assertFalse(relinker._is_ppi_ancestor(part_qr,"},{"line_number":4455,"context_line":"                                                   next_part_fgn,"},{"line_number":4456,"context_line":"                                                   max_num_ppi\u003d1))"}],"source_content_type":"text/x-python","patch_set":12,"id":"581363b2_0f5e992e","line":4453,"in_reply_to":"289719ae_855a7f53","updated":"2026-08-21 05:26:05.000000000","message":"idk, i\u0027m having trouble re-remembering what this test does - does it still read intuatively to you?\n\nTest has:\n\n```\nnext_part_fgn \u003d utils.get_partition_for_hash(hash_fgn, PART_POWER + 1)\n```\n\nI suggested:\n\n```\npart_fgn \u003d utils.get_partition_for_hash(hash_fgn, PART_POWER)\n```\n\ni.e. the \"reason\" it\u0027s \"foriegn\" has nothing to *do* with PPI ancestory and everything to do with:\n\n```\nself._create_object(policy, part_qr, hash_fgn)\n```\n\ni.e. `[next_]part_fgn` is completly meaningless/unrelated when you\u0027re just going to go stick it it *someone elses part* anyway!?\n\nmaybe what I\u0027m missing is expecting `PART_POWER \u003d\u003d ring.part_power` in the context of a audit crank turn - but at this point of the test setup we haven\u0027t done the prepare/increase/finish yet, i.e. the `part_qr` in `PART_POWER` is about to become `prev_part_power` and `next_part_fgn` in `PART_POWER + 1` is about to become just `ring.part_power`\n\n... so that would at least make sense\n\nalternatively it might be more clear if in `test_audit_run_hashes_mixed` we either skip \"Simulate PPI\" and created the object to be quarantined in `PART_POWER - 1` or rotate the ring around to `(ring.part_power, ring.next_part_power) \u003d\u003d (9, None)` *first* and use `ring.part_power` to create hashes in the part that is \"current\" to the audit.\n\nActually maybe it would make the most sense to create a different/obviously orthogonal part from the same \"current generation PP\" and put hash_fgn in *there* (as opposed to `part_qr`) - but again I think it\u0027s not going to matter which PP we pick for `part_fgn` as long as we\u0027re putting it \"someone elses part\"\n\nBottomline I think a consistent PP across part_fgn and \"whatever part we decide to stick it in\" helps drive home the *reason* it\u0027s \"foriegn\" is NOT b/c of the PP ancestory/generation differences - it\u0027s because create the hash_fgn in someone else\u0027s part (but a part from the same generation!)","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":4450,"context_line":""},{"line_number":4451,"context_line":"        # Ensures that placing `hash_fgn` in `part_qr` indeed"},{"line_number":4452,"context_line":"        # makes it foreign"},{"line_number":4453,"context_line":"        next_part_fgn \u003d utils.get_partition_for_hash(hash_fgn, PART_POWER + 1)"},{"line_number":4454,"context_line":"        self.assertFalse(relinker._is_ppi_ancestor(part_qr,"},{"line_number":4455,"context_line":"                                                   next_part_fgn,"},{"line_number":4456,"context_line":"                                                   max_num_ppi\u003d1))"}],"source_content_type":"text/x-python","patch_set":12,"id":"810b40fa_8e5cfa50","line":4453,"in_reply_to":"581363b2_0f5e992e","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":4450,"context_line":""},{"line_number":4451,"context_line":"        # Ensures that placing `hash_fgn` in `part_qr` indeed"},{"line_number":4452,"context_line":"        # makes it foreign"},{"line_number":4453,"context_line":"        next_part_fgn \u003d utils.get_partition_for_hash(hash_fgn, PART_POWER + 1)"},{"line_number":4454,"context_line":"        self.assertFalse(relinker._is_ppi_ancestor(part_qr,"},{"line_number":4455,"context_line":"                                                   next_part_fgn,"},{"line_number":4456,"context_line":"                                                   max_num_ppi\u003d1))"}],"source_content_type":"text/x-python","patch_set":12,"id":"289719ae_855a7f53","line":4453,"in_reply_to":"87866afd_172c4432","updated":"2026-05-12 17:09:08.000000000","message":"\u003e I think having `part + 1` is \"also correct\" but having it more closely \"mirror\" the previous setup for `part_qr` might drive home the real magic comes next:\n\nI can\u0027t see why mirroring the previous step adds clarity. I wrote the assertion on L4454 using `next_part_fgn` because for me it reads more naturally in the context of a PPI: the hash is found in a partition that is not an ancestor of where it is expected to be `next_part_fgn` vs. the hash is found in a partition that is not an ancestor of is should have been found `part_fgn`.\n\nAm I completely missing the point?","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4463,"context_line":"        self.rb.prepare_increase_partition_power()"},{"line_number":4464,"context_line":"        self.rb.increase_partition_power()"},{"line_number":4465,"context_line":"        self.rb.finish_increase_partition_power()"},{"line_number":4466,"context_line":"        self._save_ring()"},{"line_number":4467,"context_line":""},{"line_number":4468,"context_line":"        with self._mock_relinker():"},{"line_number":4469,"context_line":"            self.assertEqual(0, relinker.main(["}],"source_content_type":"text/x-python","patch_set":12,"id":"7e5a7985_2ae33db1","line":4466,"updated":"2026-05-06 14:47:24.000000000","message":"for funzies (after PPI) you might also try doing another:\n\n```\n        part_correct \u003d utils.get_partition_for_hash(\n            hash_fgn, self.rb.part_power)\n        _ \u003d self._create_object(policy, part_correct, hash_fgn)\n```\n\nor something to capture: same \"foreign\" hash in the *correct* part is ok/noop","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":false,"context_lines":[{"line_number":4463,"context_line":"        self.rb.prepare_increase_partition_power()"},{"line_number":4464,"context_line":"        self.rb.increase_partition_power()"},{"line_number":4465,"context_line":"        self.rb.finish_increase_partition_power()"},{"line_number":4466,"context_line":"        self._save_ring()"},{"line_number":4467,"context_line":""},{"line_number":4468,"context_line":"        with self._mock_relinker():"},{"line_number":4469,"context_line":"            self.assertEqual(0, relinker.main(["}],"source_content_type":"text/x-python","patch_set":12,"id":"50430d5e_68926dd0","line":4466,"in_reply_to":"7e5a7985_2ae33db1","updated":"2026-09-17 23:40:56.000000000","message":"I think it all worked out - the stats helped.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4498,"context_line":"                               relinker.RECON_RELINKER_FILE), \u0027rt\u0027) as f:"},{"line_number":4499,"context_line":"            recon_state \u003d json.load(f)"},{"line_number":4500,"context_line":"            stats \u003d recon_state[\"devices\"][self.existing_device][\"stats\"]"},{"line_number":4501,"context_line":"            self.assertEqual(stats[\"hash_dirs\"], 2)"},{"line_number":4502,"context_line":"            self.assertEqual(stats[\"removed\"], 1)"},{"line_number":4503,"context_line":"            self.assertEqual(stats[\"errors\"], 0)"},{"line_number":4504,"context_line":"            self.assertEqual(stats[\"warnings\"], 1)"}],"source_content_type":"text/x-python","patch_set":12,"id":"df6974a8_71686359","line":4501,"updated":"2026-05-06 14:47:24.000000000","message":"i\u0027m not sure the conditional increment of `hash_dirs` was intentional - maybe the best we could do now is add a counter for `skipped_hash_dirs` or something","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":false,"context_lines":[{"line_number":4498,"context_line":"                               relinker.RECON_RELINKER_FILE), \u0027rt\u0027) as f:"},{"line_number":4499,"context_line":"            recon_state \u003d json.load(f)"},{"line_number":4500,"context_line":"            stats \u003d recon_state[\"devices\"][self.existing_device][\"stats\"]"},{"line_number":4501,"context_line":"            self.assertEqual(stats[\"hash_dirs\"], 2)"},{"line_number":4502,"context_line":"            self.assertEqual(stats[\"removed\"], 1)"},{"line_number":4503,"context_line":"            self.assertEqual(stats[\"errors\"], 0)"},{"line_number":4504,"context_line":"            self.assertEqual(stats[\"warnings\"], 1)"}],"source_content_type":"text/x-python","patch_set":12,"id":"1681df4f_2d8bc08b","line":4501,"in_reply_to":"728e0432_d2c97b57","updated":"2026-08-21 05:26:05.000000000","message":"\u003e i\u0027m not sure the conditional increment of hash_dirs was intentional\n\nI wonder what I ment there, as this test is written I would expect the part_qr to always get processed and both hashes should !\u003d expected_hash","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":4498,"context_line":"                               relinker.RECON_RELINKER_FILE), \u0027rt\u0027) as f:"},{"line_number":4499,"context_line":"            recon_state \u003d json.load(f)"},{"line_number":4500,"context_line":"            stats \u003d recon_state[\"devices\"][self.existing_device][\"stats\"]"},{"line_number":4501,"context_line":"            self.assertEqual(stats[\"hash_dirs\"], 2)"},{"line_number":4502,"context_line":"            self.assertEqual(stats[\"removed\"], 1)"},{"line_number":4503,"context_line":"            self.assertEqual(stats[\"errors\"], 0)"},{"line_number":4504,"context_line":"            self.assertEqual(stats[\"warnings\"], 1)"}],"source_content_type":"text/x-python","patch_set":12,"id":"728e0432_d2c97b57","line":4501,"in_reply_to":"df6974a8_71686359","updated":"2026-05-12 17:09:08.000000000","message":"I think `skipped_hash_dirs` is useful. I think the right time to make this change is in a follow-up patch that also addresses this issue in `process_location`.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4503,"context_line":"            self.assertEqual(stats[\"errors\"], 0)"},{"line_number":4504,"context_line":"            self.assertEqual(stats[\"warnings\"], 1)"},{"line_number":4505,"context_line":""},{"line_number":4506,"context_line":"    def test_prev_part_power_mismatch_raises(self):"},{"line_number":4507,"context_line":"        r \u003d self._create_relinker(relinker.Step.CLEANUP)"},{"line_number":4508,"context_line":"        r.states \u003d {"},{"line_number":4509,"context_line":"            \"prev_part_power\": PART_POWER - 2,"}],"source_content_type":"text/x-python","patch_set":12,"id":"23b120a4_4a9f5e9d","line":4506,"updated":"2026-05-06 14:47:24.000000000","message":"ah, here we go - `test_\u003csomething\u003e_raises` ...\n\n... and it does have a `self.assertRaises`","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":4503,"context_line":"            self.assertEqual(stats[\"errors\"], 0)"},{"line_number":4504,"context_line":"            self.assertEqual(stats[\"warnings\"], 1)"},{"line_number":4505,"context_line":""},{"line_number":4506,"context_line":"    def test_prev_part_power_mismatch_raises(self):"},{"line_number":4507,"context_line":"        r \u003d self._create_relinker(relinker.Step.CLEANUP)"},{"line_number":4508,"context_line":"        r.states \u003d {"},{"line_number":4509,"context_line":"            \"prev_part_power\": PART_POWER - 2,"}],"source_content_type":"text/x-python","patch_set":12,"id":"cfba7bc6_c54b5e7a","line":4506,"in_reply_to":"23b120a4_4a9f5e9d","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4504,"context_line":"            self.assertEqual(stats[\"warnings\"], 1)"},{"line_number":4505,"context_line":""},{"line_number":4506,"context_line":"    def test_prev_part_power_mismatch_raises(self):"},{"line_number":4507,"context_line":"        r \u003d self._create_relinker(relinker.Step.CLEANUP)"},{"line_number":4508,"context_line":"        r.states \u003d {"},{"line_number":4509,"context_line":"            \"prev_part_power\": PART_POWER - 2,"},{"line_number":4510,"context_line":"            \"part_power\": PART_POWER,"}],"source_content_type":"text/x-python","patch_set":12,"id":"a177ab26_6b5f6f26","line":4507,"updated":"2026-05-06 14:47:24.000000000","message":"step\u003dcleanup creates a relinker with `part_power \u003d PART_POWER + 1`\n\n```\n(Pdb) !r.part_power\n9\n```","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":4504,"context_line":"            self.assertEqual(stats[\"warnings\"], 1)"},{"line_number":4505,"context_line":""},{"line_number":4506,"context_line":"    def test_prev_part_power_mismatch_raises(self):"},{"line_number":4507,"context_line":"        r \u003d self._create_relinker(relinker.Step.CLEANUP)"},{"line_number":4508,"context_line":"        r.states \u003d {"},{"line_number":4509,"context_line":"            \"prev_part_power\": PART_POWER - 2,"},{"line_number":4510,"context_line":"            \"part_power\": PART_POWER,"}],"source_content_type":"text/x-python","patch_set":12,"id":"d4b64a26_c446a4b3","line":4507,"in_reply_to":"a177ab26_6b5f6f26","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4506,"context_line":"    def test_prev_part_power_mismatch_raises(self):"},{"line_number":4507,"context_line":"        r \u003d self._create_relinker(relinker.Step.CLEANUP)"},{"line_number":4508,"context_line":"        r.states \u003d {"},{"line_number":4509,"context_line":"            \"prev_part_power\": PART_POWER - 2,"},{"line_number":4510,"context_line":"            \"part_power\": PART_POWER,"},{"line_number":4511,"context_line":"            \"next_part_power\": PART_POWER,"},{"line_number":4512,"context_line":"            \"state\": {},"}],"source_content_type":"text/x-python","patch_set":12,"id":"ff5e978a_83b229d2","line":4509,"updated":"2026-05-06 14:47:24.000000000","message":"oic, and the problem here is `partitions_filter` expected `prev_part_power \u003d PART_POWER` (i.e. `r.part_power - 1`)","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":4506,"context_line":"    def test_prev_part_power_mismatch_raises(self):"},{"line_number":4507,"context_line":"        r \u003d self._create_relinker(relinker.Step.CLEANUP)"},{"line_number":4508,"context_line":"        r.states \u003d {"},{"line_number":4509,"context_line":"            \"prev_part_power\": PART_POWER - 2,"},{"line_number":4510,"context_line":"            \"part_power\": PART_POWER,"},{"line_number":4511,"context_line":"            \"next_part_power\": PART_POWER,"},{"line_number":4512,"context_line":"            \"state\": {},"}],"source_content_type":"text/x-python","patch_set":12,"id":"37895f6d_b684c759","line":4509,"in_reply_to":"ff5e978a_83b229d2","updated":"2026-05-12 17:09:08.000000000","message":"Acknowledged","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4508,"context_line":"        r.states \u003d {"},{"line_number":4509,"context_line":"            \"prev_part_power\": PART_POWER - 2,"},{"line_number":4510,"context_line":"            \"part_power\": PART_POWER,"},{"line_number":4511,"context_line":"            \"next_part_power\": PART_POWER,"},{"line_number":4512,"context_line":"            \"state\": {},"},{"line_number":4513,"context_line":"        }"},{"line_number":4514,"context_line":"        with self.assertRaises(RuntimeError) as cm:"}],"source_content_type":"text/x-python","patch_set":12,"id":"2ba049a5_117cf52c","line":4511,"updated":"2026-05-06 14:47:24.000000000","message":"it\u0027s not super clear to me what if any state the `relinker cleanup` would expect here\n\nwhen `relinker relink` ran `part_power\u003d8` and `next_part_power\u003d9`\nwhen `relinker cleanup` started `part_power\u003d9` and `next_part_power\u003d9`\n\n... probably in no case would `next_part_powever\u003d8`; so maybe we\u0027re trying to show \"none of this state file non-sense matters and shouldn\u0027t drive the relinker *behavior* because all the state tracking is just \"best effort\" anyway\"?","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":4508,"context_line":"        r.states \u003d {"},{"line_number":4509,"context_line":"            \"prev_part_power\": PART_POWER - 2,"},{"line_number":4510,"context_line":"            \"part_power\": PART_POWER,"},{"line_number":4511,"context_line":"            \"next_part_power\": PART_POWER,"},{"line_number":4512,"context_line":"            \"state\": {},"},{"line_number":4513,"context_line":"        }"},{"line_number":4514,"context_line":"        with self.assertRaises(RuntimeError) as cm:"}],"source_content_type":"text/x-python","patch_set":12,"id":"68e809f7_f3afcc12","line":4511,"in_reply_to":"2ba049a5_117cf52c","updated":"2026-05-12 17:09:08.000000000","message":"I\u0027ve dropped this test since we\u0027re not enforcing this invariant anymore.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4546,"context_line":"                                  relinker.STATE_FILE.format(datadir\u003ddatadir))"},{"line_number":4547,"context_line":"        with open(state_file, \u0027rt\u0027) as f:"},{"line_number":4548,"context_line":"            state \u003d json.load(f)[\"state\"]"},{"line_number":4549,"context_line":"            self.assertTrue(state[str(self.part)])"},{"line_number":4550,"context_line":""},{"line_number":4551,"context_line":"        self._relink_test(((\u0027data\u0027, 0),),"},{"line_number":4552,"context_line":"                          None,"}],"source_content_type":"text/x-python","patch_set":12,"id":"0178b10c_89f3ade6","line":4549,"updated":"2026-05-06 14:47:24.000000000","message":"should we try to say something about the *part_power values in the status dict?\n\n```\n(Pdb) !status\n{\u0027part_power\u0027: 9, \u0027next_part_power\u0027: None, \u0027state\u0027: {\u0027204\u0027: True}}\n```","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":false,"context_lines":[{"line_number":4546,"context_line":"                                  relinker.STATE_FILE.format(datadir\u003ddatadir))"},{"line_number":4547,"context_line":"        with open(state_file, \u0027rt\u0027) as f:"},{"line_number":4548,"context_line":"            state \u003d json.load(f)[\"state\"]"},{"line_number":4549,"context_line":"            self.assertTrue(state[str(self.part)])"},{"line_number":4550,"context_line":""},{"line_number":4551,"context_line":"        self._relink_test(((\u0027data\u0027, 0),),"},{"line_number":4552,"context_line":"                          None,"}],"source_content_type":"text/x-python","patch_set":12,"id":"a82077ed_8a6c49c9","line":4549,"in_reply_to":"0178b10c_89f3ade6","updated":"2026-05-12 17:09:08.000000000","message":"Done","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"fff178b9b476191e801a912e33e64b05f99a0dee","unresolved":true,"context_lines":[{"line_number":4551,"context_line":"        self._relink_test(((\u0027data\u0027, 0),),"},{"line_number":4552,"context_line":"                          None,"},{"line_number":4553,"context_line":"                          ((\u0027data\u0027, 0),),"},{"line_number":4554,"context_line":"                          ((\u0027data\u0027, 0),))"},{"line_number":4555,"context_line":""},{"line_number":4556,"context_line":""},{"line_number":4557,"context_line":"if __name__ \u003d\u003d \u0027__main__\u0027:"}],"source_content_type":"text/x-python","patch_set":12,"id":"8a9539dd_9c432c56","line":4554,"updated":"2026-05-06 14:47:24.000000000","message":"what is the point of this?  inside of here under `_setup_object` we\u0027re doing a `shutil.rmtree(objects_dir, ignore_errors\u003dTrue)`\n\nas best I can tell the data here is completely disconnected from the object we just successfully audited:\n\n```\n+ {\u0027next_part_power\u0027: 10, \u0027part_power\u0027: 9, \u0027state\u0027: {\u0027165\u0027: True}}\n```","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":false,"context_lines":[{"line_number":4551,"context_line":"        self._relink_test(((\u0027data\u0027, 0),),"},{"line_number":4552,"context_line":"                          None,"},{"line_number":4553,"context_line":"                          ((\u0027data\u0027, 0),),"},{"line_number":4554,"context_line":"                          ((\u0027data\u0027, 0),))"},{"line_number":4555,"context_line":""},{"line_number":4556,"context_line":""},{"line_number":4557,"context_line":"if __name__ \u003d\u003d \u0027__main__\u0027:"}],"source_content_type":"text/x-python","patch_set":12,"id":"083787e4_ae5b3f2c","line":4554,"in_reply_to":"48eef0de_d4641812","updated":"2026-09-17 23:40:56.000000000","message":"I think I just forgot that relinker state files are obove the objects_dir 👍\n\n```\n  /srv/node/sda1/\n  ├── objects/                 # policy 0 object data\n  ├── relink.objects.json      # policy 0 relinker state\n  ├── objects-1/               # policy 1 object data\n  └── relink.objects-1.json    # policy 1 relinker state\n```\n\nthis looks right to me and the relinker doesn\u0027t care since:\n\n```\n247  -\u003e         except (ValueError, TypeError, KeyError):\n248                 # Bad state file: remove the file to restart from scratch\n249                 os.unlink(state_file)\n```\n\n```\n(Pdb) l\n234                                       STATE_FILE.format(datadir\u003dself.datadir))\n235             self.states[\"state\"].clear()\n236             try:\n237                 with open(state_file, \u0027rt\u0027) as f:\n238                     state_from_disk \u003d json.load(f)\n239 B-\u003e                 if state_from_disk[\"next_part_power\"] !\u003d \\\n240                             self.states[\"next_part_power\"]:\n241                         raise ValueError\n242                     on_disk_part_power \u003d state_from_disk[\"part_power\"]\n243                     if on_disk_part_power !\u003d self.states[\"part_power\"]:\n244                         self.states[\"prev_part_power\"] \u003d on_disk_part_power\n(Pdb) state_from_disk\n{\u0027part_power\u0027: 9, \u0027next_part_power\u0027: None, \u0027state\u0027: {\u0027149\u0027: True}}\n(Pdb) self.step\n\u003cStep.RELINK: \u0027relink\u0027\u003e\n(Pdb) \n```","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"f74beb1012862939c06faf6d2079f53ac35f3eee","unresolved":true,"context_lines":[{"line_number":4551,"context_line":"        self._relink_test(((\u0027data\u0027, 0),),"},{"line_number":4552,"context_line":"                          None,"},{"line_number":4553,"context_line":"                          ((\u0027data\u0027, 0),),"},{"line_number":4554,"context_line":"                          ((\u0027data\u0027, 0),))"},{"line_number":4555,"context_line":""},{"line_number":4556,"context_line":""},{"line_number":4557,"context_line":"if __name__ \u003d\u003d \u0027__main__\u0027:"}],"source_content_type":"text/x-python","patch_set":12,"id":"48eef0de_d4641812","line":4554,"in_reply_to":"8a9539dd_9c432c56","updated":"2026-05-12 17:09:08.000000000","message":"It ensures that the state file produced by an `AUDIT` run does not break a subsequent relink run.","commit_id":"473e66886d6199d6fe673ce34c7154e80f412b16"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"359721965b1b9e042b6173754be652c2ae9341af","unresolved":true,"context_lines":[{"line_number":4248,"context_line":"            f\"[step\u003d{step}] Finished relinker\"))"},{"line_number":4249,"context_line":""},{"line_number":4250,"context_line":"    def test_is_ppi_ancestor_identity(self):"},{"line_number":4251,"context_line":"        self.assertFalse(relinker._is_ppi_ancestor(5, 5))"},{"line_number":4252,"context_line":"        with self.assertRaises(ValueError):"},{"line_number":4253,"context_line":"            relinker._is_ppi_ancestor(-1, -1)"},{"line_number":4254,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"1ff564cc_8a6cacdc","line":4251,"updated":"2026-05-26 13:30:44.000000000","message":"Suggestion: If the (0,0) special case is intentional, consider adding a test that explicitly covers it for documentation purposes.","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d4595622d317c8bb7f125e87b70d95abb4d386b5","unresolved":false,"context_lines":[{"line_number":4248,"context_line":"            f\"[step\u003d{step}] Finished relinker\"))"},{"line_number":4249,"context_line":""},{"line_number":4250,"context_line":"    def test_is_ppi_ancestor_identity(self):"},{"line_number":4251,"context_line":"        self.assertFalse(relinker._is_ppi_ancestor(5, 5))"},{"line_number":4252,"context_line":"        with self.assertRaises(ValueError):"},{"line_number":4253,"context_line":"            relinker._is_ppi_ancestor(-1, -1)"},{"line_number":4254,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"a9315a71_8ff2ce4c","line":4251,"in_reply_to":"1ff564cc_8a6cacdc","updated":"2026-07-30 03:06:03.000000000","message":"It\u0027s not really a special case in the sense that it generically follows from the part power increase rules. There\u0027s a test for it in `test_is_ppi_ancestor_partition_zero`.","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38496,"name":"Andressa Cabistani","display_name":"Andressa","email":"acabistani@gmail.com","username":"andressadotpy","status":"I\u0027m a Software Engineer at Red Hat and I love Open Source and connect with people! Feel free to DM through IRC, I\u0027ll be delighted to chat"},"change_message_id":"359721965b1b9e042b6173754be652c2ae9341af","unresolved":true,"context_lines":[{"line_number":4252,"context_line":"        with self.assertRaises(ValueError):"},{"line_number":4253,"context_line":"            relinker._is_ppi_ancestor(-1, -1)"},{"line_number":4254,"context_line":""},{"line_number":4255,"context_line":"    def test_is_ppi_ancestor_direct(self):"},{"line_number":4256,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(3, 6, 1))"},{"line_number":4257,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(3, 7, 1))"},{"line_number":4258,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"07633c52_5f998e4e","line":4255,"updated":"2026-05-26 13:30:44.000000000","message":"Excellent test coverage for `_is_ppi_ancestor()`!","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"d4595622d317c8bb7f125e87b70d95abb4d386b5","unresolved":false,"context_lines":[{"line_number":4252,"context_line":"        with self.assertRaises(ValueError):"},{"line_number":4253,"context_line":"            relinker._is_ppi_ancestor(-1, -1)"},{"line_number":4254,"context_line":""},{"line_number":4255,"context_line":"    def test_is_ppi_ancestor_direct(self):"},{"line_number":4256,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(3, 6, 1))"},{"line_number":4257,"context_line":"        self.assertTrue(relinker._is_ppi_ancestor(3, 7, 1))"},{"line_number":4258,"context_line":""}],"source_content_type":"text/x-python","patch_set":14,"id":"4e701955_1c5a68f1","line":4255,"in_reply_to":"07633c52_5f998e4e","updated":"2026-07-30 03:06:03.000000000","message":"Thanks!","commit_id":"49ab5ded587fbab2f0f6ff93f218ca6e76d88cb4"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":550,"context_line":"            (\u00277\u0027, None, 10, 7),"},{"line_number":551,"context_line":"            (\u00271\u0027, 1, 10, 1),"},{"line_number":552,"context_line":"            (\u002710\u0027, 1, 10, 10),"},{"line_number":553,"context_line":"            (-2, None, -1, -2),"},{"line_number":554,"context_line":"        )"},{"line_number":555,"context_line":"        for value, lower_bound, upper_bound, expected in test_cases:"},{"line_number":556,"context_line":"            self.assertEqual("}],"source_content_type":"text/x-python","patch_set":19,"id":"3a571685_6340e1f8","line":553,"updated":"2026-08-21 05:26:05.000000000","message":"ohhhh, b/c there is no lower bound\n\nto be clear `-2` works here as input just as well, no?  it\u0027s just not how this helper gets USED in the context of the relinker module\n\nHeck, this test method doesn\u0027t even create a Relinker!","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":550,"context_line":"            (\u00277\u0027, None, 10, 7),"},{"line_number":551,"context_line":"            (\u00271\u0027, 1, 10, 1),"},{"line_number":552,"context_line":"            (\u002710\u0027, 1, 10, 10),"},{"line_number":553,"context_line":"            (-2, None, -1, -2),"},{"line_number":554,"context_line":"        )"},{"line_number":555,"context_line":"        for value, lower_bound, upper_bound, expected in test_cases:"},{"line_number":556,"context_line":"            self.assertEqual("}],"source_content_type":"text/x-python","patch_set":19,"id":"65191294_abcfafbd","line":553,"in_reply_to":"3a571685_6340e1f8","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":605,"context_line":"            \u0027partitions\u0027: set(),"},{"line_number":606,"context_line":"            \u0027recon_cache_path\u0027: \u0027/var/cache/swift\u0027,"},{"line_number":607,"context_line":"            \u0027stats_interval\u0027: 300.0,"},{"line_number":608,"context_line":"            \u0027max_audit_history_quarantine_threshold\u0027: 2,"},{"line_number":609,"context_line":"            \u0027clobber_hardlink_collisions\u0027: False,"},{"line_number":610,"context_line":"        }"},{"line_number":611,"context_line":"        mock_relinker.assert_called_once_with("}],"source_content_type":"text/x-python","patch_set":19,"id":"eaaab7b5_73de4215","line":608,"updated":"2026-08-21 05:26:05.000000000","message":"oic!  exp_conf must be updated","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":605,"context_line":"            \u0027partitions\u0027: set(),"},{"line_number":606,"context_line":"            \u0027recon_cache_path\u0027: \u0027/var/cache/swift\u0027,"},{"line_number":607,"context_line":"            \u0027stats_interval\u0027: 300.0,"},{"line_number":608,"context_line":"            \u0027max_audit_history_quarantine_threshold\u0027: 2,"},{"line_number":609,"context_line":"            \u0027clobber_hardlink_collisions\u0027: False,"},{"line_number":610,"context_line":"        }"},{"line_number":611,"context_line":"        mock_relinker.assert_called_once_with("}],"source_content_type":"text/x-python","patch_set":19,"id":"cbb52020_b51a3146","line":608,"in_reply_to":"eaaab7b5_73de4215","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":654,"context_line":"            \u0027workers\u0027: \u0027auto\u0027,"},{"line_number":655,"context_line":"            \u0027recon_cache_path\u0027: \u0027/var/cache/swift-foo\u0027,"},{"line_number":656,"context_line":"            \u0027stats_interval\u0027: 111.0,"},{"line_number":657,"context_line":"            \u0027max_audit_history_quarantine_threshold\u0027: 7,"},{"line_number":658,"context_line":"            \u0027clobber_hardlink_collisions\u0027: False,"},{"line_number":659,"context_line":"        }, mock.ANY, [\u0027sdx\u0027], step\u003drelinker.Step.RELINK)"},{"line_number":660,"context_line":"        logger \u003d mock_relinker.call_args[0][1]"}],"source_content_type":"text/x-python","patch_set":19,"id":"f824d2e6_74608e39","line":657,"updated":"2026-08-21 05:26:05.000000000","message":"give string conf, get int - reasonable!","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":654,"context_line":"            \u0027workers\u0027: \u0027auto\u0027,"},{"line_number":655,"context_line":"            \u0027recon_cache_path\u0027: \u0027/var/cache/swift-foo\u0027,"},{"line_number":656,"context_line":"            \u0027stats_interval\u0027: 111.0,"},{"line_number":657,"context_line":"            \u0027max_audit_history_quarantine_threshold\u0027: 7,"},{"line_number":658,"context_line":"            \u0027clobber_hardlink_collisions\u0027: False,"},{"line_number":659,"context_line":"        }, mock.ANY, [\u0027sdx\u0027], step\u003drelinker.Step.RELINK)"},{"line_number":660,"context_line":"        logger \u003d mock_relinker.call_args[0][1]"}],"source_content_type":"text/x-python","patch_set":19,"id":"24a74038_64060097","line":657,"in_reply_to":"f824d2e6_74608e39","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":674,"context_line":"                    \u0027--partition\u0027, \u0027123\u0027, \u0027--partition\u0027, \u0027456\u0027,"},{"line_number":675,"context_line":"                    \u0027--workers\u0027, \u00272\u0027,"},{"line_number":676,"context_line":"                    \u0027--stats-interval\u0027, \u0027222\u0027,"},{"line_number":677,"context_line":"                    \u0027--max-audit-history-quarantine-threshold\u0027, \u00273\u0027,"},{"line_number":678,"context_line":"                ])"},{"line_number":679,"context_line":"        mock_relinker.assert_called_once_with({"},{"line_number":680,"context_line":"            \u0027__file__\u0027: mock.ANY,"}],"source_content_type":"text/x-python","patch_set":19,"id":"5120c8f2_48ce8c6f","line":677,"updated":"2026-08-21 05:26:05.000000000","message":"cli arg gives conf value - i\u0027m seeing the vision!","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":674,"context_line":"                    \u0027--partition\u0027, \u0027123\u0027, \u0027--partition\u0027, \u0027456\u0027,"},{"line_number":675,"context_line":"                    \u0027--workers\u0027, \u00272\u0027,"},{"line_number":676,"context_line":"                    \u0027--stats-interval\u0027, \u0027222\u0027,"},{"line_number":677,"context_line":"                    \u0027--max-audit-history-quarantine-threshold\u0027, \u00273\u0027,"},{"line_number":678,"context_line":"                ])"},{"line_number":679,"context_line":"        mock_relinker.assert_called_once_with({"},{"line_number":680,"context_line":"            \u0027__file__\u0027: mock.ANY,"}],"source_content_type":"text/x-python","patch_set":19,"id":"9dcee2d6_532e885e","line":677,"in_reply_to":"5120c8f2_48ce8c6f","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":825,"context_line":""},{"line_number":826,"context_line":"        # test invalid max_audit_history_quarantine_threshold values"},{"line_number":827,"context_line":"        for threshold in (-1, 0, 33):"},{"line_number":828,"context_line":"            with self.assertRaises(SystemExit) as cm:"},{"line_number":829,"context_line":"                relinker.main(["},{"line_number":830,"context_line":"                    \u0027audit\u0027,"},{"line_number":831,"context_line":"                    \u0027--max-audit-history-quarantine-threshold\u0027, str(threshold),"}],"source_content_type":"text/x-python","patch_set":19,"id":"861af83b_48968cc6","line":828,"updated":"2026-08-21 05:26:05.000000000","message":"oh, not a ValueError even - that argparse is pretty smart!\n\nFWIW I think this loop would look better as a standalone named test method - and then mirrored for invalid values *in the conf file*.","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":825,"context_line":""},{"line_number":826,"context_line":"        # test invalid max_audit_history_quarantine_threshold values"},{"line_number":827,"context_line":"        for threshold in (-1, 0, 33):"},{"line_number":828,"context_line":"            with self.assertRaises(SystemExit) as cm:"},{"line_number":829,"context_line":"                relinker.main(["},{"line_number":830,"context_line":"                    \u0027audit\u0027,"},{"line_number":831,"context_line":"                    \u0027--max-audit-history-quarantine-threshold\u0027, str(threshold),"}],"source_content_type":"text/x-python","patch_set":19,"id":"3e16d071_2f9c92fb","line":828,"in_reply_to":"861af83b_48968cc6","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"df460dc5895694a97e9719d196493763e87ebab3","unresolved":true,"context_lines":[{"line_number":1113,"context_line":"        info_lines \u003d self.logger.get_lines_for_level(\u0027info\u0027)"},{"line_number":1114,"context_line":"        self.assertIn(\u0027[step\u003drelink] 1 hash dirs processed \u0027"},{"line_number":1115,"context_line":"                      \u0027(1 files, 1 linked, 0 removed, \u0027"},{"line_number":1116,"context_line":"                      \u00270 quarantined, 0 errors)\u0027, info_lines)"},{"line_number":1117,"context_line":""},{"line_number":1118,"context_line":"    def test_relink_data_meta_files(self):"},{"line_number":1119,"context_line":"        self._relink_test(((\u0027data\u0027, 0), (\u0027meta\u0027, 1)),"}],"source_content_type":"text/x-python","patch_set":19,"id":"f66a6501_74484c9a","line":1116,"updated":"2026-08-21 05:26:05.000000000","message":"kind of annoying churn - and audit always reports `0 linked, 0 removed` - very strange","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":38767,"name":"Wael Halbawi","display_name":"Wael Halbawi","email":"whalbawi@nvidia.com","username":"whalbawi"},"change_message_id":"a45eb74b18e87c016b0e98364b8e607acfc9f03f","unresolved":false,"context_lines":[{"line_number":1113,"context_line":"        info_lines \u003d self.logger.get_lines_for_level(\u0027info\u0027)"},{"line_number":1114,"context_line":"        self.assertIn(\u0027[step\u003drelink] 1 hash dirs processed \u0027"},{"line_number":1115,"context_line":"                      \u0027(1 files, 1 linked, 0 removed, \u0027"},{"line_number":1116,"context_line":"                      \u00270 quarantined, 0 errors)\u0027, info_lines)"},{"line_number":1117,"context_line":""},{"line_number":1118,"context_line":"    def test_relink_data_meta_files(self):"},{"line_number":1119,"context_line":"        self._relink_test(((\u0027data\u0027, 0), (\u0027meta\u0027, 1)),"}],"source_content_type":"text/x-python","patch_set":19,"id":"f9af9aa2_f25d3e37","line":1116,"in_reply_to":"f66a6501_74484c9a","updated":"2026-09-17 04:22:20.000000000","message":"Acknowledged","commit_id":"2a9d1644047c6ed06ef59e7bd689ecfc2f774209"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":161,"context_line":"        self.part \u003d part"},{"line_number":162,"context_line":"        self.next_part \u003d next_part"},{"line_number":163,"context_line":"        self.obj_path \u003d obj_path"},{"line_number":164,"context_line":"        objects_dir \u003d self._recreate_objects_dir(policy)"},{"line_number":165,"context_line":"        self.objdir, self.object_fname, self.obj_ts \u003d self._create_object("},{"line_number":166,"context_line":"            policy, part, _hash, ext)"},{"line_number":167,"context_line":""}],"source_content_type":"text/x-python","patch_set":22,"id":"6b2711e3_60e16e40","line":164,"updated":"2026-09-17 23:40:56.000000000","message":"don\u0027t get it twisted: this does NOT remove the status file.","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":230,"context_line":"                self.assertRaises(SystemExit) as cm:"},{"line_number":231,"context_line":"            relinker.main([relinker.Step.AUDIT.value, self.conf_file])"},{"line_number":232,"context_line":""},{"line_number":233,"context_line":"        self.assertEqual(2, cm.exception.code)"},{"line_number":234,"context_line":"        self.assertIn(\u0027--policy is required when running in audit mode\u0027,"},{"line_number":235,"context_line":"                      err.getvalue())"},{"line_number":236,"context_line":""}],"source_content_type":"text/x-python","patch_set":22,"id":"4ecc1ec2_3cb0889d","line":233,"updated":"2026-09-17 23:40:56.000000000","message":"this actually is NOT relinker.EXIT_NO_APPLICABLE_POLICY - it\u0027s only EQUAL to relinker.EXIT_NO_APPLICABLE_POLICY\n\n... which is kind of funny for me\n\nhttps://docs.python.org/3/library/argparse.html#argparse.ArgumentParser.error","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":244,"context_line":"            relinker.main([relinker.Step.AUDIT.value, self.conf_file,"},{"line_number":245,"context_line":"                          \u0027--policy\u0027, \u00270\u0027, \u0027--policy\u0027, \u00270\u0027, \u0027--policy\u0027, \u00272\u0027])"},{"line_number":246,"context_line":"        mock_relinker.assert_called_once()"},{"line_number":247,"context_line":"        policies \u003d mock_relinker.call_args[0][0][\u0027policies\u0027]"},{"line_number":248,"context_line":"        self.assertEqual({POLICIES[0], POLICIES[2]}, policies)"},{"line_number":249,"context_line":""},{"line_number":250,"context_line":"    def test_workers_parent(self):"}],"source_content_type":"text/x-python","patch_set":22,"id":"71245217_0c2817fa","line":247,"updated":"2026-09-17 23:40:56.000000000","message":"oh!! it\u0027s the *conf*\n\nthe first arg to the first call is a dictionary with the key `policies`","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":511,"context_line":"        self._do_test_relinker_drop_privileges(relinker.Step.RELINK)"},{"line_number":512,"context_line":"        self._do_test_relinker_drop_privileges(relinker.Step.CLEANUP)"},{"line_number":513,"context_line":"        self._do_test_relinker_drop_privileges(relinker.Step.AUDIT)"},{"line_number":514,"context_line":""},{"line_number":515,"context_line":"    def _do_test_relinker_files_per_second(self, command):"},{"line_number":516,"context_line":"        # no files per second"},{"line_number":517,"context_line":"        with mock.patch(\u0027swift.cli.relinker.RateLimitedIterator\u0027) as it:"}],"source_content_type":"text/x-python","patch_set":22,"id":"34166964_7541fb1d","line":514,"updated":"2026-09-17 23:40:56.000000000","message":"maybe slightly better if the input to the helper became `cmd_args`\n\n```\n[RELINK],\n[CLEANUP],\n[AUDIT, \u0027--policy\u0027, \u00270\u0027],\n```","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":3100,"context_line":"            get_prefixed_swift_logger(self.logger,"},{"line_number":3101,"context_line":"                                      f\u0027[step\u003d{step.value}] \u0027),"},{"line_number":3102,"context_line":"            [self.existing_device], step)"},{"line_number":3103,"context_line":"        r.conf[\u0027max_audit_history_quarantine_threshold\u0027] \u003d 1"},{"line_number":3104,"context_line":"        r.part_power \u003d PART_POWER + 1 if step \u003d\u003d relinker.Step.CLEANUP \\"},{"line_number":3105,"context_line":"            else PART_POWER"},{"line_number":3106,"context_line":"        r.next_part_power \u003d None if step \u003d\u003d relinker.Step.AUDIT \\"}],"source_content_type":"text/x-python","patch_set":22,"id":"c733f5f8_629fad8b","line":3103,"updated":"2026-09-17 23:40:56.000000000","message":"this is SUPA surprising hard-coded magic value for common test infra ... what if we just *didn\u0027t* set max_audit_history_quarantine_threshold and let all the tests use the DEFAULT?\n\n... also maybe it doesn\u0027t DO anything b/c the relinker is already created?","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":3100,"context_line":"            get_prefixed_swift_logger(self.logger,"},{"line_number":3101,"context_line":"                                      f\u0027[step\u003d{step.value}] \u0027),"},{"line_number":3102,"context_line":"            [self.existing_device], step)"},{"line_number":3103,"context_line":"        r.conf[\u0027max_audit_history_quarantine_threshold\u0027] \u003d 1"},{"line_number":3104,"context_line":"        r.part_power \u003d PART_POWER + 1 if step \u003d\u003d relinker.Step.CLEANUP \\"},{"line_number":3105,"context_line":"            else PART_POWER"},{"line_number":3106,"context_line":"        r.next_part_power \u003d None if step \u003d\u003d relinker.Step.AUDIT \\"}],"source_content_type":"text/x-python","patch_set":22,"id":"56e206d8_73468ec6","line":3103,"updated":"2026-09-17 23:40:56.000000000","message":"what\u0027s neat about the \"reach directly into conf for options\" is there is not \"object creation normalization\" step - so that if you DON\u0027T add this you get a KeyError ... not during Relinker *init* - but during `audit_location`!\n\nthe subset of required options above are JUST the \"this helper is only used with internal methods that need these\" - when you try to use this new infra with something that creates the ratelimiter: you\u0027ll get a keyerror and need to add `files_per_second: 0`\n\nthe default value of 1 here is what caught my eye.","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":4524,"context_line":"        self.assertEqual(r.stats[\u0027removed\u0027], 0)"},{"line_number":4525,"context_line":"        self.assertEqual(r.stats[\u0027quarantined\u0027], 1)"},{"line_number":4526,"context_line":"        self.assertEqual(r.stats[\u0027errors\u0027], 0)"},{"line_number":4527,"context_line":"        self.assertEqual(r.stats[\u0027warnings\u0027], 0)"},{"line_number":4528,"context_line":""},{"line_number":4529,"context_line":"    def test_audit_location_ancestor_quarantine_error(self):"},{"line_number":4530,"context_line":"        r \u003d self._create_relinker(relinker.Step.AUDIT)"}],"source_content_type":"text/x-python","patch_set":22,"id":"7a975b24_d2de9b6c","line":4527,"updated":"2026-09-17 23:40:56.000000000","message":"oic, this test \"combines\" two calls to `r.audit_location` - only ONE of them changes stats - the \"stats_combine\" isn\u0027t really trying to ... idk \"prove\" some sort of \"aggregation\" *behavior* - the name of the test is just ... what\u0027s happening.","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":4585,"context_line":"            ]))"},{"line_number":4586,"context_line":""},{"line_number":4587,"context_line":"        hashes_filter.assert_called_once_with(mock.ANY,"},{"line_number":4588,"context_line":"                                              self.suffix_dir, [self._hash])"},{"line_number":4589,"context_line":""},{"line_number":4590,"context_line":"        self.assertIn("},{"line_number":4591,"context_line":"            \u0027[step\u003daudit] 0 hash dirs processed \u0027"}],"source_content_type":"text/x-python","patch_set":22,"id":"4144fac0_8f00afc5","line":4588,"updated":"2026-09-17 23:40:56.000000000","message":"\u003e every yielded hash has already satisfied new_hash_path !\u003d hash_path\n\nmaybe someday we\u0027ll know what to do about the location generators\u0027 hashes-filter - it\u0027s confusing and wasted cognative load everytime I read the code that makes me THINK it works one way; when in reality it works a different way","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":4588,"context_line":"                                              self.suffix_dir, [self._hash])"},{"line_number":4589,"context_line":""},{"line_number":4590,"context_line":"        self.assertIn("},{"line_number":4591,"context_line":"            \u0027[step\u003daudit] 0 hash dirs processed \u0027"},{"line_number":4592,"context_line":"            \u0027(0 files, 0 linked, 0 removed, 0 quarantined, \u0027"},{"line_number":4593,"context_line":"            \u00270 warnings, 0 errors)\u0027,"},{"line_number":4594,"context_line":"            self.logger.get_lines_for_level(\u0027info\u0027))"}],"source_content_type":"text/x-python","patch_set":22,"id":"a6dee6a2_01f6277c","line":4591,"updated":"2026-09-17 23:40:56.000000000","message":"`def test_audit_location_hash_filter_prefilters_even_tho_no_one_reads_like_it_does`","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":4659,"context_line":"            self.assertEqual(stats[\"removed\"], 0)"},{"line_number":4660,"context_line":"            self.assertEqual(stats[\"quarantined\"], 1)"},{"line_number":4661,"context_line":"            self.assertEqual(stats[\"errors\"], 0)"},{"line_number":4662,"context_line":"            self.assertEqual(stats[\"warnings\"], 0)"},{"line_number":4663,"context_line":""},{"line_number":4664,"context_line":"    def test_audit_run_hashes_mixed(self):"},{"line_number":4665,"context_line":"        policy \u003d 0"}],"source_content_type":"text/x-python","patch_set":22,"id":"79db5f5a_7872f71a","line":4662,"updated":"2026-09-17 23:40:56.000000000","message":"I like this test - assertions on the quarantine file, the state file, the *recon* file - KUDOS","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"},{"author":{"_account_id":1179,"name":"Clay Gerrard","email":"clay.gerrard@gmail.com","username":"clay-gerrard"},"change_message_id":"f068bd3aea842392c408649213ccebb3108d5007","unresolved":true,"context_lines":[{"line_number":4729,"context_line":"            self.assertEqual(stats[\"removed\"], 0)"},{"line_number":4730,"context_line":"            self.assertEqual(stats[\"quarantined\"], 1)"},{"line_number":4731,"context_line":"            self.assertEqual(stats[\"errors\"], 0)"},{"line_number":4732,"context_line":"            self.assertEqual(stats[\"warnings\"], 1)"},{"line_number":4733,"context_line":""},{"line_number":4734,"context_line":"    def test_audit_run_stale_and_current_hashes_same_suffix(self):"},{"line_number":4735,"context_line":"        policy \u003d 0"}],"source_content_type":"text/x-python","patch_set":22,"id":"5954bb9e_dc0c0559","line":4732,"updated":"2026-09-17 23:40:56.000000000","message":"i like this stats - 2 processed: one quarantined, one warning!","commit_id":"0e38b8901576d88c0099e6ef28082014b4e4dd38"}]}
