)]}'
{"/COMMIT_MSG":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"d7aef9cfeaa1c4b298c59fa9c48fdafb2ca2372f","unresolved":false,"context_lines":[{"line_number":1,"context_line":"Parent:     ca9414f9 (Audit Pipeline Object and DBAPI)"},{"line_number":2,"context_line":"Author:     Douglas Viroel \u003cviroel@gmail.com\u003e"},{"line_number":3,"context_line":"AuthorDate: 2026-08-05 09:52:07 -0300"},{"line_number":4,"context_line":"Commit:     Douglas Viroel \u003cviroel@gmail.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":7,"id":"e3498743_e239df40","line":1,"updated":"2026-08-18 14:13:26.000000000","message":"The commit message bullet states \u0027Hook notifications into AuditPipeline.create(), .save(), .soft_delete() and PipelineHandler.execute() (start/end/error)\u0027. However, no PipelineHandler class exists anywhere in the tree, and send_action_notification is not called from any code path; the execution start/end/error notifications are only exercised by unit tests. A later paragraph does say send_action_notification is \u0027not yet wired in this commit\u0027, so the message contradicts itself and its own implementation, leaving the permanent history inaccurate about what this patch does.\n\n**Severity**: WARNING | **Confidence**: 0.9\n\n**Impact**: Future readers and release-note authors relying on the commit history will believe execution-event notifications are wired into the decision engine when they are not, which can hide that audit_pipeline.execution.* events will never be emitted by the current tree.\n\n**Suggestion**:\nAmend the message to remove the PipelineHandler.execute() claim (or state that the execution start/end/error notifications are added but not yet emitted from any handler), keeping the sentence that send_action_notification is for future use.","commit_id":"5a4f8cb4275f15b158b23b737634425c567a9968"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"227dd7704490c661718b5bbebd66ff77167246ef","unresolved":false,"context_lines":[{"line_number":1,"context_line":"Parent:     ca9414f9 (Audit Pipeline Object and DBAPI)"},{"line_number":2,"context_line":"Author:     Douglas Viroel \u003cviroel@gmail.com\u003e"},{"line_number":3,"context_line":"AuthorDate: 2026-08-05 09:52:07 -0300"},{"line_number":4,"context_line":"Commit:     Douglas Viroel \u003cviroel@gmail.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":7,"id":"56c0f0ee_2775dcb5","line":1,"in_reply_to":"e3498743_e239df40","updated":"2026-08-19 20:13:06.000000000","message":"it is being used in other patches in the chain","commit_id":"5a4f8cb4275f15b158b23b737634425c567a9968"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"48b7dcd45baea3d981298aa725734f83f06d1c1f","unresolved":false,"context_lines":[{"line_number":1,"context_line":"Parent:     bbcfe5be (Audit Pipeline Object and DBAPI)"},{"line_number":2,"context_line":"Author:     Douglas Viroel \u003cviroel@gmail.com\u003e"},{"line_number":3,"context_line":"AuthorDate: 2026-08-05 09:52:07 -0300"},{"line_number":4,"context_line":"Commit:     Douglas Viroel \u003cviroel@gmail.com\u003e"}],"source_content_type":"text/x-gerrit-commit-message","patch_set":8,"id":"d1868e9b_db5ad83d","line":1,"updated":"2026-08-19 20:47:47.000000000","message":"The commit message bullet states notifications are hooked into \u0027AuditPipeline.create(), .save(), .soft_delete() and PipelineHandler.execute() (start/end/error)\u0027. However, no PipelineHandler class or module exists anywhere in the tree (grep over watcher/ finds no match), and notifications.audit_pipeline.send_action_notification has no production callers outside the new unit tests. The message itself contradicts this bullet later: \u0027The send_action_notification provided in this patch if for future use of Audit Pipeline feature, but not yet wired in this commit.\u0027 One of the two statements is wrong; the accurate one is the latter. A reviewer or future archaeologist reading the permanent history would wrongly conclude execution start/end/error events are emitted today.\n\n**Severity**: WARNING | **Confidence**: 0.95\n\n**Impact**: The permanent change history is technically inaccurate about emitted events: operators or consumers auditing versioned notifications (e.g., waiting for audit_pipeline.execution.start/end/error) would believe those events are produced by this release when they are not, since no code path calls send_action_notification yet.\n\n**Suggestion**:\nReword the bullet to remove \u0027and PipelineHandler.execute() (start/end/error)\u0027 (or explicitly state the execution notifications are introduced but not yet wired to any executor, matching the later sentence), and fix \u0027if for\u0027 to \u0027is for\u0027 before merge.","commit_id":"0089aeb15c2016b193a22c6dece7efcb8432491a"}],"doc/notification_samples/audit-pipeline-delete.json":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"9911dc75c4211942cd2e0e923838b7e20238db76","unresolved":false,"context_lines":[{"line_number":1,"context_line":"{"},{"line_number":2,"context_line":"  \"priority\": \"INFO\","},{"line_number":3,"context_line":"  \"payload\": {"},{"line_number":4,"context_line":"    \"watcher_object.data\": {"}],"source_content_type":"application/json","patch_set":6,"id":"945c09fe_b87a29b9","line":1,"updated":"2026-08-18 13:32:06.000000000","message":"The newly added sample files contain message_id strings with non-hexadecimal characters (e.g. \u00273f6c4fgh-5c5c-6d5c-be9g-3c4d5e6f7a8b\u0027, \u00272e5b3efg-...\u0027, \u00274a7d5ghi-...\u0027, \u00276c9f7ijk-...\u0027), which are not valid UUIDs and break the convention of every other sample in doc/notification_samples/ (all use proper UUIDs for message_id, since oslo versionednotification serializes message_id as a UUID). Additionally, audit-pipeline-delete.json shows a populated deleted_at and stage content that the actual send_delete wiring cannot currently produce (see the soft_delete refresh issue), and audit-pipeline-execution-error.json\u0027s fault module_name references watcher.decision_engine.audit.pipeline, a module that does not exist in the tree.\n\n**Severity**: SUGGESTION | **Confidence**: 0.85\n\n**Impact**: The samples double as the machine-checked notification contract (they are loaded via the notification_sample decorator and consumed as API documentation); invalid UUIDs are sloppy documentation, and payload content that the code cannot actually emit misleads consumers integrating against these events.\n\n**Recommendation**:\nReplace the message_id values with real UUIDs, align audit-pipeline-delete.json with the payload the code path actually emits (after fixing the refresh), and either correct or defer the execution-error sample\u0027s module_name until the emitting handler exists.","commit_id":"ca8a370c127333bcbc2fca7b479eb7b2104ced32"}],"doc/notification_samples/audit-pipeline-execution-start.json":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"d7aef9cfeaa1c4b298c59fa9c48fdafb2ca2372f","unresolved":false,"context_lines":[{"line_number":22,"context_line":"  \"publisher_id\": \"infra-optim:localhost\","},{"line_number":23,"context_line":"  \"timestamp\": \"2016-10-18T09:52:05.219414\","},{"line_number":24,"context_line":"  \"event_type\": \"audit_pipeline.execution.start\","},{"line_number":25,"context_line":"  \"message_id\": \"4a7d5ghi-6d6d-7e6d-cf0h-4d5e6f7a8b9c\""},{"line_number":26,"context_line":"}"}],"source_content_type":"application/json","patch_set":7,"id":"25d9e5de_65791ce8","line":25,"updated":"2026-08-18 14:13:26.000000000","message":"Five of the six new doc/notification_samples/audit-pipeline-*.json files use message_id strings containing non-hexadecimal characters (g, h, i, j), e.g. \u00274a7d5ghi-6d6d-7e6d-cf0h-4d5e6f7a8b9c\u0027. These samples are the published reference for the new versioned notifications (consumed by documentation generation), and oslo.messaging generates real UUIDs for message_id, so the examples show a value format that can never occur.\n\n**Severity**: SUGGESTION | **Confidence**: 0.9\n\n**Impact**: Published documentation examples show malformed identifiers for the new event types; minor consumer confusion or failed copy-paste validation, no runtime impact.\n\n**Recommendation**:\nReplace the five malformed message_id strings with valid UUIDs, e.g. generate with uuid.uuid4(), matching the create sample\u0027s format.","commit_id":"5a4f8cb4275f15b158b23b737634425c567a9968"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"227dd7704490c661718b5bbebd66ff77167246ef","unresolved":false,"context_lines":[{"line_number":22,"context_line":"  \"publisher_id\": \"infra-optim:localhost\","},{"line_number":23,"context_line":"  \"timestamp\": \"2016-10-18T09:52:05.219414\","},{"line_number":24,"context_line":"  \"event_type\": \"audit_pipeline.execution.start\","},{"line_number":25,"context_line":"  \"message_id\": \"4a7d5ghi-6d6d-7e6d-cf0h-4d5e6f7a8b9c\""},{"line_number":26,"context_line":"}"}],"source_content_type":"application/json","patch_set":7,"id":"459faf00_5bd956cf","line":25,"in_reply_to":"25d9e5de_65791ce8","updated":"2026-08-19 20:13:06.000000000","message":"Fixes in next PS!","commit_id":"5a4f8cb4275f15b158b23b737634425c567a9968"}],"watcher/notifications/audit_pipeline.py":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"09a9e5b62d34853123d36f9d048888f1b5998bf3","unresolved":false,"context_lines":[{"line_number":324,"context_line":"    notification.emit(context)"},{"line_number":325,"context_line":""},{"line_number":326,"context_line":""},{"line_number":327,"context_line":"def send_action_notification("},{"line_number":328,"context_line":"    context,"},{"line_number":329,"context_line":"    audit_pipeline,"},{"line_number":330,"context_line":"    action,"}],"source_content_type":"text/x-python","patch_set":4,"id":"772e6110_5977b5a0","line":327,"updated":"2026-08-12 23:03:24.000000000","message":"The commit message states it hooks notifications into PipelineHandler.execute() for start/end/error events, and the send_action_notification function plus execution-start/end/error sample JSONs are added. However, no PipelineHandler class exists anywhere in the codebase, and send_action_notificat...\n\n**Severity**: WARNING | **Confidence**: 0.8\n\n**Impact**: The commit message describes execution notifications as a delivered feature, but they are dead code in production. If this is intentional for a partial implementation (the blueprint is \u0027Partially-implements\u0027), the commit message should not claim the wiring is done.\n\n**Suggestion**:\nEither add the execution notification calls in the pipeline execution path (when PipelineHandler is implemented), or clarify the commit message to indicate that send_action_notification is provided for future use but not yet wired. The current commit message is misleading.","commit_id":"458b21722f4621e22bcb3e3045e952b040de70ce"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"8a4296ef60a1ba4c04c561a6f791665d7096c982","unresolved":false,"context_lines":[{"line_number":324,"context_line":"    notification.emit(context)"},{"line_number":325,"context_line":""},{"line_number":326,"context_line":""},{"line_number":327,"context_line":"def send_action_notification("},{"line_number":328,"context_line":"    context,"},{"line_number":329,"context_line":"    audit_pipeline,"},{"line_number":330,"context_line":"    action,"}],"source_content_type":"text/x-python","patch_set":4,"id":"9d7f93b2_8fb3fa33","line":327,"in_reply_to":"772e6110_5977b5a0","updated":"2026-08-13 15:38:35.000000000","message":"going to be fixed in commit message","commit_id":"458b21722f4621e22bcb3e3045e952b040de70ce"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"9911dc75c4211942cd2e0e923838b7e20238db76","unresolved":false,"context_lines":[{"line_number":300,"context_line":"    notification.emit(context)"},{"line_number":301,"context_line":""},{"line_number":302,"context_line":""},{"line_number":303,"context_line":"def send_delete("},{"line_number":304,"context_line":"    context, audit_pipeline, stages\u003dNone, service\u003d\u0027infra-optim\u0027, host\u003dNone"},{"line_number":305,"context_line":"):"},{"line_number":306,"context_line":"    \"\"\"Emit an audit_pipeline.delete notification.\"\"\""}],"source_content_type":"text/x-python","patch_set":6,"id":"884b5cac_f43f4c50","line":303,"updated":"2026-08-18 13:32:06.000000000","message":"send_action_notification() and AuditPipelineActionNotification (decorated with the three audit-pipeline-execution-*.json samples) have no caller in the entire tree: grep for \u0027audit_pipeline.send\u0027 and \u0027send_action_notification\u0027 outside tests and the module itself finds only the create/update/delete hooks in watcher/objects/audit_pipeline.py, and no PipelineHandler class exists yet. The commit message is internally inconsistent: it claims \u0027Hook notifications into ... PipelineHandler.execute() (start/end/error)\u0027 while also stating send_action_notification is \u0027not yet wired in this commit\u0027. Consumers reading doc/notification_samples/audit-pipeline-execution-*.json would reasonably expect audit_pipeline.execution.start/end/error events on master, but nothing emits them.\n\n**Severity**: SUGGESTION | **Confidence**: 0.9\n\n**Impact**: Dead code plus documentation of events that never fire; low runtime risk, but the sample contract is misleading until the pipeline handler lands, and reviewers of the follow-up patch may not realize the events are still dark.\n\n**Recommendation**:\nEither defer adding the execution sample files until the handler wiring patch (matching the stated \u0027not yet wired\u0027 intent), or add a short note to the commit message/sample review confirming the events are emitted by a later patch in the series. If the handler was meant to be wired here as the message claims, add the missing send_action_notification calls.","commit_id":"ca8a370c127333bcbc2fca7b479eb7b2104ced32"}],"watcher/objects/audit_pipeline.py":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"a5d338cb5728c0a6bf5894359148f97d2dc19503","unresolved":false,"context_lines":[{"line_number":161,"context_line":"        notifications.audit_pipeline.send_create(self._context, self)"},{"line_number":162,"context_line":""},{"line_number":163,"context_line":"    @base.remotable"},{"line_number":164,"context_line":"    def save(self):"},{"line_number":165,"context_line":"        updates \u003d self.obj_get_changes()"},{"line_number":166,"context_line":"        updates.pop(\u0027id\u0027, None)"},{"line_number":167,"context_line":"        db_obj \u003d self.dbapi.update_audit_pipeline(self.uuid, updates)"}],"source_content_type":"text/x-python","patch_set":1,"id":"e10cce2e_1faf3177","line":164,"updated":"2026-08-05 19:06:13.000000000","message":"The save() method calls self._from_db_object(self, db_obj) before reading self.old_state. Because _from_db_object sets the state field directly on self, the _obj_state property setter advances old_state to the current post-save value, so the notification always shows old_state \u003d\u003d state.\n\n**Severity**: HIGH | **Confidence**: 0.9\n\n**Risk**: Every audit_pipeline.update notification will report old_state equal to the current state, making the state_update.old_state field useless for tracking actual state transitions. Notification consumers relying on old_state to detect state changes will not see real transitions.\n\n**Priority**: Before merge\n**Why This Matters**: Every audit_pipeline.update notification will report old_state equal to the current state, making the state_update.old_state field useless for tracking actual state transitions. Notification consumers relying on old_state to detect state changes will not see real transitions.\n\n**Recommendation**:\nCapture old_state before calling _from_db_object, e.g.: old_state \u003d self.old_state; self._from_db_object(self, db_obj, eager\u003dFalse); send_update(self._context, self, old_state\u003dold_state). Alternatively, follow the Audit.save() pattern of creating a fresh object for _from_db_object and using obj_refresh().","commit_id":"8e665e1a3e40d56d9da97fc2aa206d77d11625d6"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"310a77ae199f751e7a97762adf81209fda79b4d5","unresolved":false,"context_lines":[{"line_number":161,"context_line":"        notifications.audit_pipeline.send_create(self._context, self)"},{"line_number":162,"context_line":""},{"line_number":163,"context_line":"    @base.remotable"},{"line_number":164,"context_line":"    def save(self):"},{"line_number":165,"context_line":"        updates \u003d self.obj_get_changes()"},{"line_number":166,"context_line":"        updates.pop(\u0027id\u0027, None)"},{"line_number":167,"context_line":"        db_obj \u003d self.dbapi.update_audit_pipeline(self.uuid, updates)"}],"source_content_type":"text/x-python","patch_set":2,"id":"9a5f3ad5_1f88423d","line":164,"updated":"2026-08-07 20:01:43.000000000","message":"AuditPipeline.save() calls _from_db_object(self, db_obj) before send_update, which re-sets the state field through the _obj_state proxy, advancing _old_state to the current value. As a result, the old_state field in AuditPipelineStateUpdatePayload will always equal state, making state-transition...\n\n**Severity**: HIGH | **Confidence**: 0.9\n\n**Risk**: Versioned notification consumers (e.g., telemetry, audit trails) relying on audit_pipeline.update events to detect state transitions will see old_state always equal to state, defeating the purpose of the state_update payload field. The notification sample documents correct behavior that the imple...\n\n**Priority**: Before merge\n**Why This Matters**: Versioned notification consumers (e.g., telemetry, audit trails) relying on audit_pipeline.update events to detect state transitions will see old_state always equal to state, defeating the purpose of the state_update payload field. The notification sample documents correct behavior that the imple...\n\n**Recommendation**:\nCapture old_state before calling _from_db_object. Either: (1) store old_state \u003d self.old_state before the DB refresh and pass it to send_update, or (2) follow the Audit.save() pattern: create a temporary object via self._from_db_object(self.__class__(self._context), db_obj) and use self.obj_refresh(obj) to avoid triggering the state proxy on self.","commit_id":"7989e41162d0f75321cb23b1f6bae2e717069668"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"310a77ae199f751e7a97762adf81209fda79b4d5","unresolved":false,"context_lines":[{"line_number":175,"context_line":"        current \u003d self.get_by_uuid(self._context, uuid\u003dself.uuid, eager\u003deager)"},{"line_number":176,"context_line":"        self.obj_refresh(current)"},{"line_number":177,"context_line":""},{"line_number":178,"context_line":"    @base.remotable"},{"line_number":179,"context_line":"    def soft_delete(self):"},{"line_number":180,"context_line":"        stages \u003d AuditPipelineStage.list("},{"line_number":181,"context_line":"            self._context, filters\u003d{\u0027audit_pipeline_id\u0027: self.id}"}],"source_content_type":"text/x-python","patch_set":2,"id":"4325a421_c66891dd","line":178,"updated":"2026-08-07 20:01:43.000000000","message":"AuditPipeline.soft_delete() calls dbapi.soft_delete_audit_pipeline but does not refresh the in-memory object from the database afterward. Consequently, the deleted_at field in the AuditPipelineDeletePayload notification will be null even though the pipeline was soft-deleted and the DB row has a t...\n\n**Severity**: WARNING | **Confidence**: 0.9\n\n**Impact**: Notification consumers expecting the deleted_at timestamp in audit_pipeline.delete events will receive null, causing incorrect audit trail data. The notification sample file documents a non-null deleted_at that the implementation does not produce.\n\n**Suggestion**:\nAfter calling self.dbapi.soft_delete_audit_pipeline(self.uuid), refresh the object from DB (following the Audit.soft_delete() pattern) before sending the delete notification: db_obj \u003d self.dbapi.get_audit_pipeline_by_uuid(self._context, self.uuid); obj \u003d self._from_db_object(self.__class__(self._context), db_obj); self.obj_refresh(obj).","commit_id":"7989e41162d0f75321cb23b1f6bae2e717069668"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"4472d59786f75c964ca17173cbbdd30b1096096e","unresolved":false,"context_lines":[{"line_number":169,"context_line":"        notifications.audit_pipeline.send_create(self._context, self)"},{"line_number":170,"context_line":""},{"line_number":171,"context_line":"    @base.remotable"},{"line_number":172,"context_line":"    def save(self):"},{"line_number":173,"context_line":"        updates \u003d self.obj_get_changes()"},{"line_number":174,"context_line":"        updates.pop(\u0027id\u0027, None)"},{"line_number":175,"context_line":"        db_obj \u003d self.dbapi.update_audit_pipeline(self.uuid, updates)"}],"source_content_type":"text/x-python","patch_set":3,"id":"6644198e_cb869e83","line":172,"updated":"2026-08-12 13:00:47.000000000","message":"AuditPipeline.save() calls _from_db_object(self, db_obj) which reloads the state field from the DB into the same object instance. This triggers the _obj_state setter a second time, advancing old_state to the current state. As a result, the send_update notification always reports old_state equal t...\n\n**Severity**: HIGH | **Confidence**: 0.9\n\n**Risk**: Every audit_pipeline.update notification produced via AuditPipeline.save() reports old_state \u003d\u003d state, making the state_update payload useless for consumers tracking pipeline state transitions. This also affects soft_delete() which calls save() internally.\n\n**Priority**: Before merge\n**Why This Matters**: Every audit_pipeline.update notification produced via AuditPipeline.save() reports old_state \u003d\u003d state, making the state_update payload useless for consumers tracking pipeline state transitions. This also affects soft_delete() which calls save() internally.\n\n**Recommendation**:\nFollow the Audit.save() pattern: create a new object via self._from_db_object(self.__class__(self._context), db_obj, eager\u003dFalse), call self.obj_refresh(obj), then call send_update. This preserves old_state because obj_refresh only overwrites fields whose values actually differ.","commit_id":"c5e8dbd9e47afd0ae25172e045680e016824427a"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"4472d59786f75c964ca17173cbbdd30b1096096e","unresolved":false,"context_lines":[{"line_number":184,"context_line":"        self.obj_refresh(current)"},{"line_number":185,"context_line":""},{"line_number":186,"context_line":"    @base.remotable"},{"line_number":187,"context_line":"    def soft_delete(self):"},{"line_number":188,"context_line":"        stages \u003d AuditPipelineStage.list("},{"line_number":189,"context_line":"            self._context, filters\u003d{\u0027audit_pipeline_id\u0027: self.id}"},{"line_number":190,"context_line":"        )"}],"source_content_type":"text/x-python","patch_set":3,"id":"ca27de86_a4a7d6ee","line":187,"updated":"2026-08-12 13:00:47.000000000","message":"AuditPipeline.soft_delete() calls dbapi.soft_delete_audit_pipeline() but does not refresh the object from the DB result. Consequently, the delete notification payload has deleted_at\u003dNone instead of the actual soft-delete timestamp. The existing Audit.soft_delete() correctly refreshes from the DB...\n\n**Severity**: WARNING | **Confidence**: 0.9\n\n**Impact**: Consumers of the audit_pipeline.delete notification will always see deleted_at as null, losing the timestamp of when the pipeline was soft-deleted. The notification sample file (audit-pipeline-delete.json) shows deleted_at with a timestamp, so the sample does not match actual runtime behavior.\n\n**Suggestion**:\nAfter self.dbapi.soft_delete_audit_pipeline(self.uuid), refresh the object: db_obj \u003d self.dbapi.soft_delete_audit_pipeline(self.uuid); obj \u003d self._from_db_object(self.__class__(self._context), db_obj); self.obj_refresh(obj). Then call send_delete. This matches the Audit.soft_delete() pattern.","commit_id":"c5e8dbd9e47afd0ae25172e045680e016824427a"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"222e8fc5b2f15651549fc01dd64da6ef559aa6be","unresolved":false,"context_lines":[{"line_number":166,"context_line":"        values \u003d self.obj_get_changes()"},{"line_number":167,"context_line":"        db_obj \u003d self.dbapi.create_audit_pipeline(values)"},{"line_number":168,"context_line":"        self._from_db_object(self, db_obj, eager\u003dTrue)"},{"line_number":169,"context_line":"        notifications.audit_pipeline.send_create(self._context, self)"},{"line_number":170,"context_line":""},{"line_number":171,"context_line":"    @base.remotable"},{"line_number":172,"context_line":"    def save(self):"}],"source_content_type":"text/x-python","patch_set":5,"id":"92984b54_406ebce5","line":169,"updated":"2026-08-13 20:00:29.000000000","message":"The create() method calls send_create(self._context, self) without passing stages, so the create notification payload always has an empty stages list, even when the pipeline has stages configured.\n\n**Severity**: WARNING | **Confidence**: 0.8\n\n**Impact**: Create notifications for pipelines with stages will always show an empty stages array. Consumers relying on create notifications to learn about pipeline stage composition will miss this information until a delete event fires.\n\n**Suggestion**:\nLoad stages before emitting the create notification, mirroring the soft_delete pattern: stages \u003d AuditPipelineStage.list(self._context, filters\u003d{\u0027audit_pipeline_id\u0027: self.id})","commit_id":"80ff8e4d08a44a7b8c14e28d6eb67b6c6d4d937e"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"222e8fc5b2f15651549fc01dd64da6ef559aa6be","unresolved":false,"context_lines":[{"line_number":169,"context_line":"        notifications.audit_pipeline.send_create(self._context, self)"},{"line_number":170,"context_line":""},{"line_number":171,"context_line":"    @base.remotable"},{"line_number":172,"context_line":"    def save(self):"},{"line_number":173,"context_line":"        updates \u003d self.obj_get_changes()"},{"line_number":174,"context_line":"        updates.pop(\u0027id\u0027, None)"},{"line_number":175,"context_line":"        db_obj \u003d self.dbapi.update_audit_pipeline(self.uuid, updates)"}],"source_content_type":"text/x-python","patch_set":5,"id":"c649a8c6_c190ec74","line":172,"updated":"2026-08-13 20:00:29.000000000","message":"The save() method calls self._from_db_object(self, db_obj) in-place, which re-triggers the _obj_state property setter with the post-save DB value. This causes old_state to be set to the new state rather than the previous state, so state_update in the notification reports old_state \u003d\u003d state.\n\n**Severity**: HIGH | **Confidence**: 0.9\n\n**Risk**: Consumers of audit_pipeline.update notifications will receive incorrect state transition information. The old_state field will always equal the current state, making it impossible to detect actual state transitions and defeating the purpose of the state_update payload.\n\n**Priority**: Before merge\n**Why This Matters**: Consumers of audit_pipeline.update notifications will receive incorrect state transition information. The old_state field will always equal the current state, making it impossible to detect actual state transitions and defeating the purpose of the state_update payload.\n\n**Recommendation**:\nFollow the same pattern as Audit.save() and ActionPlan.save(): create a separate object instance for _from_db_object, then merge via obj_refresh before sending the notification.","commit_id":"80ff8e4d08a44a7b8c14e28d6eb67b6c6d4d937e"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"222e8fc5b2f15651549fc01dd64da6ef559aa6be","unresolved":false,"context_lines":[{"line_number":184,"context_line":"        self.obj_refresh(current)"},{"line_number":185,"context_line":""},{"line_number":186,"context_line":"    @base.remotable"},{"line_number":187,"context_line":"    def soft_delete(self):"},{"line_number":188,"context_line":"        stages \u003d AuditPipelineStage.list("},{"line_number":189,"context_line":"            self._context, filters\u003d{\u0027audit_pipeline_id\u0027: self.id}"},{"line_number":190,"context_line":"        )"}],"source_content_type":"text/x-python","patch_set":5,"id":"dc58e023_6e0a556c","line":187,"updated":"2026-08-13 20:00:29.000000000","message":"The soft_delete() method does not refresh the object from the database after calling dbapi.soft_delete_audit_pipeline(), so the send_delete notification reports deleted_at\u003dNone despite the record being soft-deleted.\n\n**Severity**: WARNING | **Confidence**: 0.9\n\n**Impact**: Consumers of audit_pipeline.delete notifications will see deleted_at\u003dNone, making it appear the pipeline was not actually deleted, inconsistent with the notification sample and with Audit delete notifications.\n\n**Suggestion**:\nRefresh the object from DB after soft-delete, following the Audit.soft_delete() pattern: db_obj \u003d self.dbapi.soft_delete_audit_pipeline(self.uuid); obj \u003d self._from_db_object(...); self.obj_refresh(obj)","commit_id":"80ff8e4d08a44a7b8c14e28d6eb67b6c6d4d937e"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"9911dc75c4211942cd2e0e923838b7e20238db76","unresolved":false,"context_lines":[{"line_number":184,"context_line":"        self.obj_refresh(current)"},{"line_number":185,"context_line":""},{"line_number":186,"context_line":"    @base.remotable"},{"line_number":187,"context_line":"    def soft_delete(self):"},{"line_number":188,"context_line":"        stages \u003d AuditPipelineStage.list("},{"line_number":189,"context_line":"            self._context, filters\u003d{\u0027audit_pipeline_id\u0027: self.id}"},{"line_number":190,"context_line":"        )"}],"source_content_type":"text/x-python","patch_set":6,"id":"3779234d_7360b786","line":187,"updated":"2026-08-18 13:32:06.000000000","message":"AuditPipeline.soft_delete() calls self.dbapi.soft_delete_audit_pipeline(self.uuid) but discards the returned DB row and never refreshes self via _from_db_object/obj_refresh before calling notifications.audit_pipeline.send_delete(). The delete payload\u0027s SCHEMA maps deleted_at (and updated_at) directly from the in-memory object, so the emitted audit_pipeline.delete notification always carries deleted_at: null even though the row is soft-deleted in the database. This diverges from the documented sample doc/notification_samples/audit-pipeline-delete.json (which shows a populated deleted_at) and from the established Audit.soft_delete() pattern in watcher/objects/audit.py, which refreshes the object from the soft-delete result before send_delete().\n\n**Severity**: WARNING | **Confidence**: 0.85\n\n**Impact**: Consumers of versioned notifications (e.g. external billing/audit tooling that uses deleted_at to record deletion time) receive null deleted_at on every audit_pipeline.delete event, and the shipped sample documentation misrepresents the actual payload, undermining the notification contract the samples are meant to define.\n\n**Suggestion**:\nMirror Audit.soft_delete(): capture the return of dbapi.soft_delete_audit_pipeline(self.uuid), refresh self from it (e.g. obj \u003d self._from_db_object(self.__class__(self._context), db_obj, eager\u003dFalse); self.obj_refresh(obj)) before send_delete(), and regenerate/verify audit-pipeline-delete.json against the real emitted payload.","commit_id":"ca8a370c127333bcbc2fca7b479eb7b2104ced32"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"d7aef9cfeaa1c4b298c59fa9c48fdafb2ca2372f","unresolved":false,"context_lines":[{"line_number":169,"context_line":"        notifications.audit_pipeline.send_create(self._context, self)"},{"line_number":170,"context_line":""},{"line_number":171,"context_line":"    @base.remotable"},{"line_number":172,"context_line":"    def save(self):"},{"line_number":173,"context_line":"        updates \u003d self.obj_get_changes()"},{"line_number":174,"context_line":"        updates.pop(\u0027id\u0027, None)"},{"line_number":175,"context_line":"        db_obj \u003d self.dbapi.update_audit_pipeline(self.uuid, updates)"}],"source_content_type":"text/x-python","patch_set":7,"id":"c6e43906_88632388","line":172,"updated":"2026-08-18 14:13:26.000000000","message":"In AuditPipeline.save(), self._from_db_object(self, db_obj, eager\u003dFalse) is called before send_update(). _from_db_object assigns every field (obj[field] \u003d db_object[field]), which routes \u0027state\u0027 through the _obj_state property setter. That setter always advances the proxy (self._old_state, self._state \u003d self._state, value), so by the time send_update(..., old_state\u003dself.old_state) runs, old_state equals the freshly saved state and the true previous state is lost. The state_update block of every audit_pipeline.update notification therefore shows old_state \u003d\u003d state, and since send_update only populates state when old_state is set, the transition information the feature exists to publish is wrong on every state change.\n\n**Severity**: HIGH | **Confidence**: 0.9\n\n**Impact**: Every audit_pipeline.update notification (including the update emitted during soft_delete) publishes state_update with old_state equal to the new state. Consumers tracking pipeline lifecycle transitions via these versioned notifications get no valid transition data, and the shipped sample documents behavior that is never emitted.\n\n**Priority**: Before merge\n**Recommendation**:\nCapture the transition before refreshing: in save(), read old_state \u003d self.old_state before calling _from_db_object, and pass that captured value to send_update (or mirror watcher/objects/audit.py:322-342 by building a fresh object from the db result and applying it via obj_refresh, which only overwrites differing fields). Then update the assertion in test_pipeline_object_save_emits_update_notification to expect old_state\u003d\u0027PENDING\u0027.","commit_id":"5a4f8cb4275f15b158b23b737634425c567a9968"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"227dd7704490c661718b5bbebd66ff77167246ef","unresolved":false,"context_lines":[{"line_number":169,"context_line":"        notifications.audit_pipeline.send_create(self._context, self)"},{"line_number":170,"context_line":""},{"line_number":171,"context_line":"    @base.remotable"},{"line_number":172,"context_line":"    def save(self):"},{"line_number":173,"context_line":"        updates \u003d self.obj_get_changes()"},{"line_number":174,"context_line":"        updates.pop(\u0027id\u0027, None)"},{"line_number":175,"context_line":"        db_obj \u003d self.dbapi.update_audit_pipeline(self.uuid, updates)"}],"source_content_type":"text/x-python","patch_set":7,"id":"d8e3e8d6_037cd2b7","line":172,"in_reply_to":"c6e43906_88632388","updated":"2026-08-19 20:13:06.000000000","message":"Fixed in next PS!","commit_id":"5a4f8cb4275f15b158b23b737634425c567a9968"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"d7aef9cfeaa1c4b298c59fa9c48fdafb2ca2372f","unresolved":false,"context_lines":[{"line_number":184,"context_line":"        self.obj_refresh(current)"},{"line_number":185,"context_line":""},{"line_number":186,"context_line":"    @base.remotable"},{"line_number":187,"context_line":"    def soft_delete(self):"},{"line_number":188,"context_line":"        stages \u003d AuditPipelineStage.list("},{"line_number":189,"context_line":"            self._context, filters\u003d{\u0027audit_pipeline_id\u0027: self.id}"},{"line_number":190,"context_line":"        )"}],"source_content_type":"text/x-python","patch_set":7,"id":"cb701926_29c1413e","line":187,"updated":"2026-08-18 14:13:26.000000000","message":"AuditPipeline.soft_delete() calls self.dbapi.soft_delete_audit_pipeline(self.uuid) but discards the returned row and does not refresh the in-memory object, then sends the delete notification from the stale object. Because the preceding save() only saw the pre-soft-delete row, the emitted AuditPipelineDeletePayload carries deleted_at: null. This deviates from the established Audit pattern (watcher/objects/audit.py soft_delete refreshes from the returned db_obj via _from_db_object + obj_refresh) and contradicts the committed sample doc/notification_samples/audit-pipeline-delete.json, which documents deleted_at as a populated timestamp.\n\n**Severity**: WARNING | **Confidence**: 0.85\n\n**Impact**: The new audit_pipeline.delete notification reports deleted_at as null, giving operators/consumers an incomplete record of the deletion event and a published sample that does not match actual emissions.\n\n**Suggestion**:\nFollow the Audit pattern: capture db_obj \u003d self.dbapi.soft_delete_audit_pipeline(self.uuid), refresh the object from it before sending (self.obj_refresh over a freshly built object, taking care to preserve old_state per CF-001), then call send_delete. Alternatively update the sample to reflect the emitted null value if the omission is intentional.","commit_id":"5a4f8cb4275f15b158b23b737634425c567a9968"},{"author":{"_account_id":30002,"name":"Douglas Viroel","email":"viroel@gmail.com","username":"dviroel"},"change_message_id":"227dd7704490c661718b5bbebd66ff77167246ef","unresolved":false,"context_lines":[{"line_number":184,"context_line":"        self.obj_refresh(current)"},{"line_number":185,"context_line":""},{"line_number":186,"context_line":"    @base.remotable"},{"line_number":187,"context_line":"    def soft_delete(self):"},{"line_number":188,"context_line":"        stages \u003d AuditPipelineStage.list("},{"line_number":189,"context_line":"            self._context, filters\u003d{\u0027audit_pipeline_id\u0027: self.id}"},{"line_number":190,"context_line":"        )"}],"source_content_type":"text/x-python","patch_set":7,"id":"93565134_d0d2fe25","line":187,"in_reply_to":"cb701926_29c1413e","updated":"2026-08-19 20:13:06.000000000","message":"Fixed in next PS!","commit_id":"5a4f8cb4275f15b158b23b737634425c567a9968"},{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"f6ca5aa4060c9106ea969aa1b2ac96623d3fc44d","unresolved":false,"context_lines":[{"line_number":169,"context_line":"        notifications.audit_pipeline.send_create(self._context, self)"},{"line_number":170,"context_line":""},{"line_number":171,"context_line":"    @base.remotable"},{"line_number":172,"context_line":"    def save(self):"},{"line_number":173,"context_line":"        updates \u003d self.obj_get_changes()"},{"line_number":174,"context_line":"        updates.pop(\u0027id\u0027, None)"},{"line_number":175,"context_line":"        db_obj \u003d self.dbapi.update_audit_pipeline(self.uuid, updates)"}],"source_content_type":"text/x-python","patch_set":9,"id":"0e345aea_dee7bbdf","line":172,"updated":"2026-08-19 23:05:23.000000000","message":"AuditPipeline.save() refreshes a fresh object and copies fields into self via obj_refresh(), but unlike the Audit.save() method it mirrors (watcher/objects/audit.py, which calls self.obj_reset_changes() after notifying), it never resets the change tracker. obj_refresh() applies updates through field assignment, which re-marks fields as changed, and _from_db_object() only resets the change tracker on the throwaway instance, not on self.\n\n**Severity**: WARNING | **Confidence**: 0.85\n\n**Impact**: After save(), the object permanently reports pending changes. Any subsequent save() re-persists the stale change set (including updated_at/created_at written back from the loaded copy, which can mask concurrent updates), and code that branches on obj_what_changed() (e.g., delta serialization over RPC or future pipeline flows built on this object) will see phantom dirty fields. The bug is silent today only because the pipeline feature is not yet wired to the API.\n\n**Suggestion**:\nAdd `self.obj_reset_changes()` at the end of AuditPipeline.save(), matching watcher/objects/audit.py Audit.save(). Optionally add an assertion in test_pipeline_object_save_emits_update_notification that `self.pipeline.obj_what_changed() \u003d\u003d set()` after save.","commit_id":"d23c18f9b1afa7c905d700eae8c4a3c5a4942aff"}],"watcher/tests/unit/notifications/test_audit_pipeline_notification.py":[{"author":{"_account_id":28006,"name":"teim-ci","display_name":"teim-ci","email":"ci@seanmooney.info","username":"ci-sean-mooney","status":"this is a third-party ci account run by sean-k-mooney on irc\nhosted at zuul.teim.app"},"tag":"autogenerated:zuul:automatic-ci","change_message_id":"222e8fc5b2f15651549fc01dd64da6ef559aa6be","unresolved":false,"context_lines":[{"line_number":420,"context_line":"    def test_pipeline_object_soft_delete_emits_delete_notification(self):"},{"line_number":421,"context_line":"        self.pipeline.soft_delete()"},{"line_number":422,"context_line":""},{"line_number":423,"context_line":"        # soft_delete() calls save() (update) then send_delete → 2 info calls."},{"line_number":424,"context_line":"        self.assertEqual(2, self.m_notifier.info.call_count)"},{"line_number":425,"context_line":"        payload \u003d self.m_notifier.info.call_args[1][\u0027payload\u0027]"},{"line_number":426,"context_line":"        # NOTE: send_delete is called after dbapi.soft_delete_audit_pipeline()"}],"source_content_type":"text/x-python","patch_set":5,"id":"b59dcb30_e803e336","line":423,"updated":"2026-08-13 20:00:29.000000000","message":"A comment in test_audit_pipeline_notification.py is 80 characters long, exceeding the project\u0027s 79-character line limit.\n\n**Severity**: SUGGESTION | **Confidence**: 0.9\n\n**Benefit**: Will cause a pep8/flake8 CI failure if E501 is enforced, potentially blocking merge.\n\n**Recommendation**:\nShorten the comment to fit within 79 characters, replacing the arrow character or rephrasing.","commit_id":"80ff8e4d08a44a7b8c14e28d6eb67b6c6d4d937e"}]}
