)]}'
{"ironic/common/exception.py":[{"author":{"_account_id":10342,"name":"Jay Faulkner","display_name":"JayF","email":"jay@jvf.cc","username":"JayF","status":"youtube.com/@oss-gr / podcast.gr-oss.io"},"change_message_id":"78016ac18b989bc40056a63646841cbeae3394b0","unresolved":true,"context_lines":[{"line_number":939,"context_line":"class NodeMaintenanceAlreadyExists(Conflict):"},{"line_number":940,"context_line":"    _msg_fmt \u003d _(\"Node %(node)s already has an active maintenance entry of \""},{"line_number":941,"context_line":"                 \"type %(type)s at %(scope)s scope asserted by project \""},{"line_number":942,"context_line":"                 \"%(project)s.\")"},{"line_number":943,"context_line":""},{"line_number":944,"context_line":""},{"line_number":945,"context_line":"class NodeMaintenanceNotFound(NotFound):"}],"source_content_type":"text/x-python","patch_set":1,"id":"80877bb0_0eafb097","line":942,"updated":"2026-08-13 17:07:26.000000000","message":"This was an explicit design decision, just highlighting some operator pain it might cause:\n\nI have fix_my_node.bash, it looks like this (pseudocode):\n```\n1) set maintenance on node\n2) attempt to run repair on node\n3) unset maintenance if #2 succeeds\n```\n\nIn the previous model, I could run this as many times as I wanted, and the second run -- even after a fail -- would continue because setting maint on a node with maint already set is a noop. After this change, however, subsequent runs of this script would throw a NodeMaintenanceAlreadyExists.\n\nJust tossing this out here so we can be 100% sure we want to break people like this. I think it\u0027s OK, but I\u0027m on the fence.","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"},{"author":{"_account_id":36770,"name":"cid","display_name":"cid","email":"cid@gr-oss.io","username":"cidelight","status":"@gr-oss upstream: Doing good IRONIC things..."},"change_message_id":"27073322ec7c4157c1ee9c5118b98664c09e07e5","unresolved":false,"context_lines":[{"line_number":939,"context_line":"class NodeMaintenanceAlreadyExists(Conflict):"},{"line_number":940,"context_line":"    _msg_fmt \u003d _(\"Node %(node)s already has an active maintenance entry of \""},{"line_number":941,"context_line":"                 \"type %(type)s at %(scope)s scope asserted by project \""},{"line_number":942,"context_line":"                 \"%(project)s.\")"},{"line_number":943,"context_line":""},{"line_number":944,"context_line":""},{"line_number":945,"context_line":"class NodeMaintenanceNotFound(NotFound):"}],"source_content_type":"text/x-python","patch_set":1,"id":"8cfff224_373b1eb1","line":942,"in_reply_to":"630c9b75_552bca3c","updated":"2026-09-15 16:24:18.000000000","message":"Acknowledged","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"},{"author":{"_account_id":10342,"name":"Jay Faulkner","display_name":"JayF","email":"jay@jvf.cc","username":"JayF","status":"youtube.com/@oss-gr / podcast.gr-oss.io"},"change_message_id":"3bef1a481ea982f222b488e17a548ade6b12c9ac","unresolved":true,"context_lines":[{"line_number":939,"context_line":"class NodeMaintenanceAlreadyExists(Conflict):"},{"line_number":940,"context_line":"    _msg_fmt \u003d _(\"Node %(node)s already has an active maintenance entry of \""},{"line_number":941,"context_line":"                 \"type %(type)s at %(scope)s scope asserted by project \""},{"line_number":942,"context_line":"                 \"%(project)s.\")"},{"line_number":943,"context_line":""},{"line_number":944,"context_line":""},{"line_number":945,"context_line":"class NodeMaintenanceNotFound(NotFound):"}],"source_content_type":"text/x-python","patch_set":1,"id":"630c9b75_552bca3c","line":942,"in_reply_to":"80877bb0_0eafb097","updated":"2026-08-24 15:58:45.000000000","message":"This was discussed in the Ironic meeting on August 24.\n\nWe agreed the best behavior was:\n- if an identical request (same type/scope/reason comes in), it should be considered successful and noop\u0027d (do not touch created_at)\n- if type/scope match, but not reason, NodeMaintenanceAlreadyExists","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"}],"ironic/common/maintenance.py":[{"author":{"_account_id":10342,"name":"Jay Faulkner","display_name":"JayF","email":"jay@jvf.cc","username":"JayF","status":"youtube.com/@oss-gr / podcast.gr-oss.io"},"change_message_id":"78016ac18b989bc40056a63646841cbeae3394b0","unresolved":true,"context_lines":[{"line_number":75,"context_line":"        user\u003dcontext.user_id, project\u003dcontext.project_id)"},{"line_number":76,"context_line":""},{"line_number":77,"context_line":""},{"line_number":78,"context_line":"def get_entries(context, node, types\u003dNone):"},{"line_number":79,"context_line":"    \"\"\"Return the node\u0027s active maintenance entries, oldest first."},{"line_number":80,"context_line":""},{"line_number":81,"context_line":"    :param context: a request context."}],"source_content_type":"text/x-python","patch_set":1,"id":"56319cb8_26e96778","line":78,"updated":"2026-08-13 17:07:26.000000000","message":"nit throughout: since this is new code, can we do type annotations?","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"},{"author":{"_account_id":36770,"name":"cid","display_name":"cid","email":"cid@gr-oss.io","username":"cidelight","status":"@gr-oss upstream: Doing good IRONIC things..."},"change_message_id":"27073322ec7c4157c1ee9c5118b98664c09e07e5","unresolved":false,"context_lines":[{"line_number":75,"context_line":"        user\u003dcontext.user_id, project\u003dcontext.project_id)"},{"line_number":76,"context_line":""},{"line_number":77,"context_line":""},{"line_number":78,"context_line":"def get_entries(context, node, types\u003dNone):"},{"line_number":79,"context_line":"    \"\"\"Return the node\u0027s active maintenance entries, oldest first."},{"line_number":80,"context_line":""},{"line_number":81,"context_line":"    :param context: a request context."}],"source_content_type":"text/x-python","patch_set":1,"id":"aaad40ed_f1dc5507","line":78,"in_reply_to":"56319cb8_26e96778","updated":"2026-09-15 16:24:18.000000000","message":"Acknowledged","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"},{"author":{"_account_id":10342,"name":"Jay Faulkner","display_name":"JayF","email":"jay@jvf.cc","username":"JayF","status":"youtube.com/@oss-gr / podcast.gr-oss.io"},"change_message_id":"78016ac18b989bc40056a63646841cbeae3394b0","unresolved":true,"context_lines":[{"line_number":111,"context_line":"    if existing:"},{"line_number":112,"context_line":"        entry \u003d existing[0]"},{"line_number":113,"context_line":"        if entry.reason !\u003d reason:"},{"line_number":114,"context_line":"            entry.reason \u003d reason"},{"line_number":115,"context_line":"            entry.save()"},{"line_number":116,"context_line":"            _record_history(context, node, entry, \u0027reason updated\u0027)"},{"line_number":117,"context_line":"        return entry"}],"source_content_type":"text/x-python","patch_set":1,"id":"0d3fc54b_9e6b81d7","line":114,"updated":"2026-08-13 17:07:26.000000000","message":"I validated this matches existing behavior: most recently set reason always wins.","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"},{"author":{"_account_id":36770,"name":"cid","display_name":"cid","email":"cid@gr-oss.io","username":"cidelight","status":"@gr-oss upstream: Doing good IRONIC things..."},"change_message_id":"27073322ec7c4157c1ee9c5118b98664c09e07e5","unresolved":false,"context_lines":[{"line_number":111,"context_line":"    if existing:"},{"line_number":112,"context_line":"        entry \u003d existing[0]"},{"line_number":113,"context_line":"        if entry.reason !\u003d reason:"},{"line_number":114,"context_line":"            entry.reason \u003d reason"},{"line_number":115,"context_line":"            entry.save()"},{"line_number":116,"context_line":"            _record_history(context, node, entry, \u0027reason updated\u0027)"},{"line_number":117,"context_line":"        return entry"}],"source_content_type":"text/x-python","patch_set":1,"id":"06bffaa3_b1555c08","line":114,"in_reply_to":"0d3fc54b_9e6b81d7","updated":"2026-09-15 16:24:18.000000000","message":"Acknowledged","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"}],"ironic/common/release_mappings.py":[{"author":{"_account_id":10342,"name":"Jay Faulkner","display_name":"JayF","email":"jay@jvf.cc","username":"JayF","status":"youtube.com/@oss-gr / podcast.gr-oss.io"},"change_message_id":"78016ac18b989bc40056a63646841cbeae3394b0","unresolved":true,"context_lines":[{"line_number":1053,"context_line":"            \u0027Node\u0027: [\u00271.44\u0027, \u00271.43\u0027, \u00271.42\u0027, \u00271.41\u0027],"},{"line_number":1054,"context_line":"            \u0027NodeHistory\u0027: [\u00271.3\u0027, \u00271.2\u0027, \u00271.1\u0027, \u00271.0\u0027],"},{"line_number":1055,"context_line":"            \u0027NodeInventory\u0027: [\u00271.1\u0027, \u00271.0\u0027],"},{"line_number":1056,"context_line":"            \u0027NodeMaintenance\u0027: [\u00271.0\u0027],"},{"line_number":1057,"context_line":"            \u0027Conductor\u0027: [\u00271.6\u0027, \u00271.5\u0027, \u00271.4\u0027],"},{"line_number":1058,"context_line":"            \u0027Chassis\u0027: [\u00271.4\u0027, \u00271.3\u0027],"},{"line_number":1059,"context_line":"            \u0027Deployment\u0027: [\u00271.1\u0027, \u00271.0\u0027],"}],"source_content_type":"text/x-python","patch_set":1,"id":"ae6fcef8_b3deed4e","line":1056,"updated":"2026-08-13 17:07:26.000000000","message":"this has to be with the migration","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"},{"author":{"_account_id":36770,"name":"cid","display_name":"cid","email":"cid@gr-oss.io","username":"cidelight","status":"@gr-oss upstream: Doing good IRONIC things..."},"change_message_id":"27073322ec7c4157c1ee9c5118b98664c09e07e5","unresolved":false,"context_lines":[{"line_number":1053,"context_line":"            \u0027Node\u0027: [\u00271.44\u0027, \u00271.43\u0027, \u00271.42\u0027, \u00271.41\u0027],"},{"line_number":1054,"context_line":"            \u0027NodeHistory\u0027: [\u00271.3\u0027, \u00271.2\u0027, \u00271.1\u0027, \u00271.0\u0027],"},{"line_number":1055,"context_line":"            \u0027NodeInventory\u0027: [\u00271.1\u0027, \u00271.0\u0027],"},{"line_number":1056,"context_line":"            \u0027NodeMaintenance\u0027: [\u00271.0\u0027],"},{"line_number":1057,"context_line":"            \u0027Conductor\u0027: [\u00271.6\u0027, \u00271.5\u0027, \u00271.4\u0027],"},{"line_number":1058,"context_line":"            \u0027Chassis\u0027: [\u00271.4\u0027, \u00271.3\u0027],"},{"line_number":1059,"context_line":"            \u0027Deployment\u0027: [\u00271.1\u0027, \u00271.0\u0027],"}],"source_content_type":"text/x-python","patch_set":1,"id":"6c4dc8b2_6fd6b3a3","line":1056,"in_reply_to":"ae6fcef8_b3deed4e","updated":"2026-09-15 16:24:18.000000000","message":"Acknowledged","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"}],"ironic/db/sqlalchemy/api.py":[{"author":{"_account_id":10342,"name":"Jay Faulkner","display_name":"JayF","email":"jay@jvf.cc","username":"JayF","status":"youtube.com/@oss-gr / podcast.gr-oss.io"},"change_message_id":"78016ac18b989bc40056a63646841cbeae3394b0","unresolved":true,"context_lines":[{"line_number":3524,"context_line":"        values.setdefault(\u0027uuid\u0027, uuidutils.generate_uuid())"},{"line_number":3525,"context_line":"        # The moment the entry was asserted, which survives a later"},{"line_number":3526,"context_line":"        # in-place update of its reason as updated_at would not."},{"line_number":3527,"context_line":"        values.setdefault(\u0027asserted_at\u0027, timeutils.utcnow())"},{"line_number":3528,"context_line":"        entry \u003d models.NodeMaintenance()"},{"line_number":3529,"context_line":"        entry.update(values)"},{"line_number":3530,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"08eff3a1_d25ce7bf","line":3527,"updated":"2026-08-13 17:07:26.000000000","message":"Does asserted_at give us any value over just using created_at? Is there some Ironic convention against using created_at here?","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"},{"author":{"_account_id":36770,"name":"cid","display_name":"cid","email":"cid@gr-oss.io","username":"cidelight","status":"@gr-oss upstream: Doing good IRONIC things..."},"change_message_id":"27073322ec7c4157c1ee9c5118b98664c09e07e5","unresolved":true,"context_lines":[{"line_number":3524,"context_line":"        values.setdefault(\u0027uuid\u0027, uuidutils.generate_uuid())"},{"line_number":3525,"context_line":"        # The moment the entry was asserted, which survives a later"},{"line_number":3526,"context_line":"        # in-place update of its reason as updated_at would not."},{"line_number":3527,"context_line":"        values.setdefault(\u0027asserted_at\u0027, timeutils.utcnow())"},{"line_number":3528,"context_line":"        entry \u003d models.NodeMaintenance()"},{"line_number":3529,"context_line":"        entry.update(values)"},{"line_number":3530,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"f32fdc71_85b504d6","line":3527,"in_reply_to":"08eff3a1_d25ce7bf","updated":"2026-09-15 16:24:18.000000000","message":"I couldn\u0027t find any reason not to, so I have swapped to the existing field.","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"}],"ironic/objects/node_maintenance.py":[{"author":{"_account_id":10342,"name":"Jay Faulkner","display_name":"JayF","email":"jay@jvf.cc","username":"JayF","status":"youtube.com/@oss-gr / podcast.gr-oss.io"},"change_message_id":"78016ac18b989bc40056a63646841cbeae3394b0","unresolved":true,"context_lines":[{"line_number":18,"context_line":""},{"line_number":19,"context_line":""},{"line_number":20,"context_line":"@base.IronicObjectRegistry.register"},{"line_number":21,"context_line":"class NodeMaintenance(base.IronicObject,"},{"line_number":22,"context_line":"                      object_base.VersionedObjectDictCompat):"},{"line_number":23,"context_line":"    \"\"\"One active maintenance entry on a node."},{"line_number":24,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"1ae013a1_6f29791b","line":21,"updated":"2026-08-13 17:07:26.000000000","message":"I believe with this change, even though we missed it in the spec, we need to consider notifications.\n\nSetting a node to maintenance can trigger one, but we don\u0027t have a specific notification payload for nodemaintenance.\n\nWe should explicitly observe the notifcation behavior before and after this change and evaluate if we need to add a specific notification object around NodeMaintenance so the extra metadata is made available to operators.","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"},{"author":{"_account_id":10342,"name":"Jay Faulkner","display_name":"JayF","email":"jay@jvf.cc","username":"JayF","status":"youtube.com/@oss-gr / podcast.gr-oss.io"},"change_message_id":"ed3512694306a3b0b4c185fb9d93df0abe1e8055","unresolved":true,"context_lines":[{"line_number":18,"context_line":""},{"line_number":19,"context_line":""},{"line_number":20,"context_line":"@base.IronicObjectRegistry.register"},{"line_number":21,"context_line":"class NodeMaintenance(base.IronicObject,"},{"line_number":22,"context_line":"                      object_base.VersionedObjectDictCompat):"},{"line_number":23,"context_line":"    \"\"\"One active maintenance entry on a node."},{"line_number":24,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"874a4229_b8b69b06","line":21,"in_reply_to":"1ae013a1_6f29791b","updated":"2026-08-20 17:21:34.000000000","message":"Highlighting this; I don\u0027t know if it was addressed in a later change.","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"},{"author":{"_account_id":36770,"name":"cid","display_name":"cid","email":"cid@gr-oss.io","username":"cidelight","status":"@gr-oss upstream: Doing good IRONIC things..."},"change_message_id":"27073322ec7c4157c1ee9c5118b98664c09e07e5","unresolved":true,"context_lines":[{"line_number":18,"context_line":""},{"line_number":19,"context_line":""},{"line_number":20,"context_line":"@base.IronicObjectRegistry.register"},{"line_number":21,"context_line":"class NodeMaintenance(base.IronicObject,"},{"line_number":22,"context_line":"                      object_base.VersionedObjectDictCompat):"},{"line_number":23,"context_line":"    \"\"\"One active maintenance entry on a node."},{"line_number":24,"context_line":""}],"source_content_type":"text/x-python","patch_set":1,"id":"0b8ce02f_bcf5c22a","line":21,"in_reply_to":"874a4229_b8b69b06","updated":"2026-09-15 16:24:18.000000000","message":"type/scope/asserted_by/asserted_by_project/extra fields on NodeMaintenance aren\u0027t surfaced anywhere in notifications (yet; that\u0027s a milestone 2 work item). NodePayload still carries the boolean and reason since this series intentionally doesn\u0027t touch the API surface; I\u0027d like to defer adding a dedicated NodeMaintenanceCRUDNotification/NodeMaintenanceCRUDPayload to that milestone, where the typed-entry API will actually need to expose this metadata anyway.\n\nNodeMaintenanceAlreadyExists does exist in this patch, but it\u0027s only reachable through the typed-entry DB-API/object layer, which has no external caller until milestone 2 wires up the typed endpoints, so it\u0027s inert from an external-behavior standpoint today.\n\nNodeMaintenanceController._set_maintenance still emits, so the legacy notification path is unchanged by this series. There should be no behavior change for anything reachable from outside this series; 0 regression for existing consumers today.","commit_id":"5323ee20e5d266812b3652e0a37e57cd972a12f5"}]}
