From 1df545079026162506009ecb2dfe71baf494aa9a Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Wed, 9 Aug 2023 11:40:00 -0300
Subject: [PATCH 01/25] style: Run Black on update_docs_from_rfc_index()

---
 ietf/sync/rfceditor.py | 136 ++++++++++++++++++++++++++++++-----------
 1 file changed, 101 insertions(+), 35 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index dab5cb17c..8dd511459 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -342,7 +342,7 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
 
     errata = {}
     for item in errata_data:
-        name = item['doc-id']
+        name = item["doc-id"]
         if not name in errata:
             errata[name] = []
         errata[name].append(item)
@@ -357,7 +357,7 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
         "Best Current Practice": StdLevelName.objects.get(slug="bcp"),
         "Historic": StdLevelName.objects.get(slug="hist"),
         "Unknown": StdLevelName.objects.get(slug="unkn"),
-        }
+    }
 
     stream_mapping = {
         "IETF": StreamName.objects.get(slug="ietf"),
@@ -367,15 +367,32 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
         "Legacy": StreamName.objects.get(slug="legacy"),
     }
 
-    tag_has_errata = DocTagName.objects.get(slug='errata')
-    tag_has_verified_errata = DocTagName.objects.get(slug='verified-errata')
+    tag_has_errata = DocTagName.objects.get(slug="errata")
+    tag_has_verified_errata = DocTagName.objects.get(slug="verified-errata")
     relationship_obsoletes = DocRelationshipName.objects.get(slug="obs")
     relationship_updates = DocRelationshipName.objects.get(slug="updates")
 
     system = Person.objects.get(name="(System)")
 
-    for rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats, pages, abstract in index_data:
-
+    for (
+        rfc_number,
+        title,
+        authors,
+        rfc_published_date,
+        current_status,
+        updates,
+        updated_by,
+        obsoletes,
+        obsoleted_by,
+        also,
+        draft,
+        has_errata,
+        stream,
+        wg,
+        file_formats,
+        pages,
+        abstract,
+    ) in index_data:
         if skip_older_than_date and rfc_published_date < skip_older_than_date:
             # speed up the process by skipping old entries
             continue
@@ -402,7 +419,9 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
 
             if not doc:
                 changes.append("created document %s" % prettify_std_name(name))
-                doc = Document.objects.create(name=name, type=DocTypeName.objects.get(slug="draft"))
+                doc = Document.objects.create(
+                    name=name, type=DocTypeName.objects.get(slug="draft")
+                )
 
             # add alias
             alias, __ = DocAlias.objects.get_or_create(name=name)
@@ -435,12 +454,16 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
             doc.stream = stream_mapping[stream]
             changes.append("changed stream to %s" % doc.stream)
 
-        if not doc.group: # if we have no group assigned, check if RFC Editor has a suggestion
+        if (
+            not doc.group
+        ):  # if we have no group assigned, check if RFC Editor has a suggestion
             if wg:
                 doc.group = Group.objects.get(acronym=wg)
                 changes.append("set group to %s" % doc.group)
             else:
-                doc.group = Group.objects.get(type="individ") # fallback for newly created doc
+                doc.group = Group.objects.get(
+                    type="individ"
+                )  # fallback for newly created doc
 
         if not doc.latest_event(type="published_rfc"):
             e = DocEvent(doc=doc, rev=doc.rev, type="published_rfc")
@@ -469,33 +492,44 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
             e.save()
             events.append(e)
 
-            changes.append("added RFC published event at %s" % e.time.strftime("%Y-%m-%d"))
+            changes.append(
+                "added RFC published event at %s" % e.time.strftime("%Y-%m-%d")
+            )
             rfc_published = True
 
-        for t in ("draft-iesg", "draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"):
+        for t in (
+            "draft-iesg",
+            "draft-stream-iab",
+            "draft-stream-irtf",
+            "draft-stream-ise",
+        ):
             prev_state = doc.get_state(t)
             if prev_state is not None:
                 if prev_state.slug not in ("pub", "idexists"):
-                    new_state = State.objects.select_related("type").get(used=True, type=t, slug="pub")
+                    new_state = State.objects.select_related("type").get(
+                        used=True, type=t, slug="pub"
+                    )
                     doc.set_state(new_state)
-                    changes.append("changed %s to %s" % (new_state.type.label, new_state))
+                    changes.append(
+                        "changed %s to %s" % (new_state.type.label, new_state)
+                    )
                     e = update_action_holders(doc, prev_state, new_state)
                     if e:
                         events.append(e)
-            elif t == 'draft-iesg':
-                doc.set_state(State.objects.get(type_id='draft-iesg', slug='idexists'))
+            elif t == "draft-iesg":
+                doc.set_state(State.objects.get(type_id="draft-iesg", slug="idexists"))
 
         def parse_relation_list(l):
             res = []
             for x in l:
-                # This lookup wasn't finding anything but maybe some STD and we know 
+                # This lookup wasn't finding anything but maybe some STD and we know
                 # if the STD had more than one RFC the wrong thing happens
                 #
-                #if x[:3] in ("NIC", "IEN", "STD", "RTR"):
+                # if x[:3] in ("NIC", "IEN", "STD", "RTR"):
                 #    # try translating this to RFCs that we can handle
                 #    # sensibly; otherwise we'll have to ignore them
                 #   l = DocAlias.objects.filter(name__startswith="rfc", docs__docalias__name=x.lower())
-                #else:
+                # else:
                 l = Document.objects.filter(name=x.lower())
 
                 for a in l:
@@ -504,14 +538,36 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
             return res
 
         for x in parse_relation_list(obsoletes):
-            if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_obsoletes):
-                r = RelatedDocument.objects.create(source=doc, target=x, relationship=relationship_obsoletes)
-                changes.append("created %s relation between %s and %s" % (r.relationship.name.lower(), prettify_std_name(r.source.name), prettify_std_name(r.target.name)))
+            if not RelatedDocument.objects.filter(
+                source=doc, target=x, relationship=relationship_obsoletes
+            ):
+                r = RelatedDocument.objects.create(
+                    source=doc, target=x, relationship=relationship_obsoletes
+                )
+                changes.append(
+                    "created %s relation between %s and %s"
+                    % (
+                        r.relationship.name.lower(),
+                        prettify_std_name(r.source.name),
+                        prettify_std_name(r.target.name),
+                    )
+                )
 
         for x in parse_relation_list(updates):
-            if not RelatedDocument.objects.filter(source=doc, target=x, relationship=relationship_updates):
-                r = RelatedDocument.objects.create(source=doc, target=x, relationship=relationship_updates)
-                changes.append("created %s relation between %s and %s" % (r.relationship.name.lower(), prettify_std_name(r.source.name), prettify_std_name(r.target.name)))
+            if not RelatedDocument.objects.filter(
+                source=doc, target=x, relationship=relationship_updates
+            ):
+                r = RelatedDocument.objects.create(
+                    source=doc, target=x, relationship=relationship_updates
+                )
+                changes.append(
+                    "created %s relation between %s and %s"
+                    % (
+                        r.relationship.name.lower(),
+                        prettify_std_name(r.source.name),
+                        prettify_std_name(r.target.name),
+                    )
+                )
 
         if also:
             for a in also:
@@ -520,14 +576,21 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
                     DocAlias.objects.create(name=a).docs.add(doc)
                     changes.append("created alias %s" % prettify_std_name(a))
 
-        doc_errata = errata.get('RFC%04d'%rfc_number, [])
-        all_rejected = doc_errata and all( er['errata_status_code']=='Rejected' for er in doc_errata )
+        doc_errata = errata.get("RFC%04d" % rfc_number, [])
+        all_rejected = doc_errata and all(
+            er["errata_status_code"] == "Rejected" for er in doc_errata
+        )
         if has_errata and not all_rejected:
             if not doc.tags.filter(pk=tag_has_errata.pk).exists():
                 doc.tags.add(tag_has_errata)
                 changes.append("added Errata tag")
-            has_verified_errata = any([ er['errata_status_code']=='Verified' for er in doc_errata ])
-            if has_verified_errata and not doc.tags.filter(pk=tag_has_verified_errata.pk).exists():
+            has_verified_errata = any(
+                [er["errata_status_code"] == "Verified" for er in doc_errata]
+            )
+            if (
+                has_verified_errata
+                and not doc.tags.filter(pk=tag_has_verified_errata.pk).exists()
+            ):
                 doc.tags.add(tag_has_verified_errata)
                 changes.append("added Verified Errata tag")
         else:
@@ -542,13 +605,16 @@ def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=Non
                 changes.append("removed Verified Errata tag")
 
         if changes:
-            events.append(DocEvent.objects.create(
-                doc=doc,
-                rev=doc.rev,
-                by=system,
-                type="sync_from_rfc_editor",
-                desc="Received changes through RFC Editor sync (%s)" % ", ".join(changes),
-            ))
+            events.append(
+                DocEvent.objects.create(
+                    doc=doc,
+                    rev=doc.rev,
+                    by=system,
+                    type="sync_from_rfc_editor",
+                    desc="Received changes through RFC Editor sync (%s)"
+                    % ", ".join(changes),
+                )
+            )
 
             doc.save_with_history(events)
 

From f464f3172d441b16f61e51fc21085d1dc5a029ec Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Wed, 9 Aug 2023 11:41:18 -0300
Subject: [PATCH 02/25] chore: Add type annotation to complex return value

---
 ietf/sync/rfceditor.py | 5 ++++-
 1 file changed, 4 insertions(+), 1 deletion(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 8dd511459..0e811c731 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -7,6 +7,7 @@ import datetime
 import re
 import requests
 
+from typing import Iterator
 from urllib.parse import urlencode
 from xml.dom import pulldom, Node
 
@@ -332,7 +333,9 @@ def parse_index(response):
     return data
 
 
-def update_docs_from_rfc_index(index_data, errata_data, skip_older_than_date=None):
+def update_docs_from_rfc_index(
+    index_data, errata_data, skip_older_than_date=None
+) -> Iterator[tuple[list[str], Document, bool]]:
     """Given parsed data from the RFC Editor index, update the documents in the database
 
     Yields a list of change descriptions for each document, if any.

From 87bd9bbd6d4339d4ab210d9c18ac1f080a7ae273 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 10:36:49 -0300
Subject: [PATCH 03/25] feat: Use rfc doc type when updating from rfc index

---
 ietf/sync/rfceditor.py | 181 +++++++++++++++++++++++------------------
 1 file changed, 103 insertions(+), 78 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 0e811c731..101b33369 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -7,7 +7,7 @@ import datetime
 import re
 import requests
 
-from typing import Iterator
+from typing import Iterator, Optional
 from urllib.parse import urlencode
 from xml.dom import pulldom, Node
 
@@ -388,7 +388,7 @@ def update_docs_from_rfc_index(
         obsoletes,
         obsoleted_by,
         also,
-        draft,
+        draft_name,
         has_errata,
         stream,
         wg,
@@ -403,66 +403,101 @@ def update_docs_from_rfc_index(
         # we assume two things can happen: we get a new RFC, or an
         # attribute has been updated at the RFC Editor (RFC Editor
         # attributes take precedence over our local attributes)
-        events = []
-        changes = []
+        rfc_events = []
+        rfc_changes = []
         rfc_published = False
+        draft_events = []
+        draft_changes = []
 
-        # make sure we got the document and alias
-        doc = None
-        name = "rfc%s" % rfc_number
-        a = DocAlias.objects.filter(name=name)
-        if a:
-            doc = a[0].document
-        else:
-            if draft:
-                try:
-                    doc = Document.objects.get(name=draft)
-                except Document.DoesNotExist:
-                    pass
-
-            if not doc:
-                changes.append("created document %s" % prettify_std_name(name))
-                doc = Document.objects.create(
-                    name=name, type=DocTypeName.objects.get(slug="draft")
-                )
-
-            # add alias
-            alias, __ = DocAlias.objects.get_or_create(name=name)
+        # Find the document
+        doc, created_rfc = Document.objects.get_or_create(
+            rfc_number=rfc_number,
+            type_id="rfc",
+            defaults={"name": f"rfc{rfc_number}"}
+        )
+        if created_rfc:
+            rfc_changes.append(f"created document {prettify_std_name(doc.name)}")
+            # Create DocAlias (for consistency until we drop DocAlias altogether)
+            alias, _ = DocAlias.objects.get_or_create(name=doc.name)
             alias.docs.add(doc)
-            changes.append("created alias %s" % prettify_std_name(name))
+            rfc_changes.append("created alias %s" % prettify_std_name(doc.name))
+
+        if draft_name:
+            try:
+                draft = Document.objects.get(name=draft_name)
+            except Document.DoesNotExist:
+                pass
+            else:
+                # Ensure the draft is in the "rfc" state and move its files to the archive
+                # if necessary.
+                if draft.get_state_slug() != "rfc":
+                    draft.set_state(State.objects.get(used=True, type="draft", slug="rfc"))
+                    move_draft_files_to_archive(draft, draft.rev)
+                    draft_changes.append(
+                        f"changed state to {draft.get_state()}"
+                    )
+
+                # Ensure the draft and rfc are linked with a "became_rfc" relationship
+                r, created_relateddoc = RelatedDocument.objects.get_or_create(
+                    source=draft, target=doc, relationship_id="became_rfc"
+                )
+                if created_relateddoc:
+                    change = "created {rel_name} relationship between {pretty_draft_name} and {pretty_rfc_name}".format(
+                        rel_name=r.relationship.name.lower(),
+                        pretty_draft_name=prettify_std_name(draft),
+                        pretty_rfc_name=prettify_std_name(doc),
+                    )
+                    draft_changes.append(change)
+                    rfc_changes.append(change)
+
+                # Ensure draft is in the correct iesg and stream states
+                for t in (
+                    "draft-iesg",
+                    "draft-stream-iab",
+                    "draft-stream-irtf",
+                    "draft-stream-ise",
+                ):
+                    prev_state = draft.get_state(t)
+                    if prev_state is not None:
+                        if prev_state.slug not in ("pub", "idexists"):
+                            new_state = State.objects.select_related("type").get(
+                                used=True, type=t, slug="pub"
+                            )
+                            draft.set_state(new_state)
+                            draft_changes.append(f"changed {new_state.type.label} to {new_state}")
+                            e = update_action_holders(draft, prev_state, new_state)
+                            if e:
+                                draft_events.append(e)
+                    elif t == "draft-iesg":
+                        draft.set_state(State.objects.get(type_id="draft-iesg", slug="idexists"))
 
         # check attributes
         if title != doc.title:
             doc.title = title
-            changes.append("changed title to '%s'" % doc.title)
+            rfc_changes.append("changed title to '%s'" % doc.title)
 
         if abstract and abstract != doc.abstract:
             doc.abstract = abstract
-            changes.append("changed abstract to '%s'" % doc.abstract)
+            rfc_changes.append("changed abstract to '%s'" % doc.abstract)
 
         if pages and int(pages) != doc.pages:
             doc.pages = int(pages)
-            changes.append("changed pages to %s" % doc.pages)
+            rfc_changes.append("changed pages to %s" % doc.pages)
 
         if std_level_mapping[current_status] != doc.std_level:
             doc.std_level = std_level_mapping[current_status]
-            changes.append("changed standardization level to %s" % doc.std_level)
-
-        if doc.get_state_slug() != "rfc":
-            doc.set_state(State.objects.get(used=True, type="draft", slug="rfc"))
-            move_draft_files_to_archive(doc, doc.rev)
-            changes.append("changed state to %s" % doc.get_state())
+            rfc_changes.append("changed standardization level to %s" % doc.std_level)
 
         if doc.stream != stream_mapping[stream]:
             doc.stream = stream_mapping[stream]
-            changes.append("changed stream to %s" % doc.stream)
+            rfc_changes.append("changed stream to %s" % doc.stream)
 
         if (
             not doc.group
         ):  # if we have no group assigned, check if RFC Editor has a suggestion
             if wg:
                 doc.group = Group.objects.get(acronym=wg)
-                changes.append("set group to %s" % doc.group)
+                rfc_changes.append("set group to %s" % doc.group)
             else:
                 doc.group = Group.objects.get(
                     type="individ"
@@ -493,35 +528,13 @@ def update_docs_from_rfc_index(
             e.by = system
             e.desc = "RFC published"
             e.save()
-            events.append(e)
+            rfc_events.append(e)
 
-            changes.append(
+            rfc_changes.append(
                 "added RFC published event at %s" % e.time.strftime("%Y-%m-%d")
             )
             rfc_published = True
 
-        for t in (
-            "draft-iesg",
-            "draft-stream-iab",
-            "draft-stream-irtf",
-            "draft-stream-ise",
-        ):
-            prev_state = doc.get_state(t)
-            if prev_state is not None:
-                if prev_state.slug not in ("pub", "idexists"):
-                    new_state = State.objects.select_related("type").get(
-                        used=True, type=t, slug="pub"
-                    )
-                    doc.set_state(new_state)
-                    changes.append(
-                        "changed %s to %s" % (new_state.type.label, new_state)
-                    )
-                    e = update_action_holders(doc, prev_state, new_state)
-                    if e:
-                        events.append(e)
-            elif t == "draft-iesg":
-                doc.set_state(State.objects.get(type_id="draft-iesg", slug="idexists"))
-
         def parse_relation_list(l):
             res = []
             for x in l:
@@ -547,7 +560,7 @@ def update_docs_from_rfc_index(
                 r = RelatedDocument.objects.create(
                     source=doc, target=x, relationship=relationship_obsoletes
                 )
-                changes.append(
+                rfc_changes.append(
                     "created %s relation between %s and %s"
                     % (
                         r.relationship.name.lower(),
@@ -563,7 +576,7 @@ def update_docs_from_rfc_index(
                 r = RelatedDocument.objects.create(
                     source=doc, target=x, relationship=relationship_updates
                 )
-                changes.append(
+                rfc_changes.append(
                     "created %s relation between %s and %s"
                     % (
                         r.relationship.name.lower(),
@@ -577,16 +590,16 @@ def update_docs_from_rfc_index(
                 a = a.lower()
                 if not DocAlias.objects.filter(name=a):
                     DocAlias.objects.create(name=a).docs.add(doc)
-                    changes.append("created alias %s" % prettify_std_name(a))
+                    rfc_changes.append("created alias %s" % prettify_std_name(a))
 
-        doc_errata = errata.get("RFC%04d" % rfc_number, [])
+        doc_errata = errata.get("RFC%04d" % rfc_number, [])  # rfc10k problem here
         all_rejected = doc_errata and all(
             er["errata_status_code"] == "Rejected" for er in doc_errata
         )
         if has_errata and not all_rejected:
             if not doc.tags.filter(pk=tag_has_errata.pk).exists():
                 doc.tags.add(tag_has_errata)
-                changes.append("added Errata tag")
+                rfc_changes.append("added Errata tag")
             has_verified_errata = any(
                 [er["errata_status_code"] == "Verified" for er in doc_errata]
             )
@@ -595,34 +608,46 @@ def update_docs_from_rfc_index(
                 and not doc.tags.filter(pk=tag_has_verified_errata.pk).exists()
             ):
                 doc.tags.add(tag_has_verified_errata)
-                changes.append("added Verified Errata tag")
+                rfc_changes.append("added Verified Errata tag")
         else:
             if doc.tags.filter(pk=tag_has_errata.pk):
                 doc.tags.remove(tag_has_errata)
                 if all_rejected:
-                    changes.append("removed Errata tag (all errata rejected)")
+                    rfc_changes.append("removed Errata tag (all errata rejected)")
                 else:
-                    changes.append("removed Errata tag")
+                    rfc_changes.append("removed Errata tag")
             if doc.tags.filter(pk=tag_has_verified_errata.pk):
                 doc.tags.remove(tag_has_verified_errata)
-                changes.append("removed Verified Errata tag")
+                rfc_changes.append("removed Verified Errata tag")
 
-        if changes:
-            events.append(
+        # Update the draft first
+        if draft_changes:
+            draft_events.append(
+                DocEvent.objects.create(
+                    doc=draft,
+                    rev=doc.rev,
+                    by=system,
+                    type="sync_from_rfc_editor",
+                    desc="Received changes through RFC Editor sync (%s)"
+                         % ", ".join(draft_changes),
+                )
+            )
+            draft.save_with_history(draft_events)
+            yield draft_changes, draft, False  # yield changes to the draft
+
+        if rfc_changes:
+            rfc_events.append(
                 DocEvent.objects.create(
                     doc=doc,
                     rev=doc.rev,
                     by=system,
                     type="sync_from_rfc_editor",
                     desc="Received changes through RFC Editor sync (%s)"
-                    % ", ".join(changes),
+                         % ", ".join(rfc_changes),
                 )
             )
-
-            doc.save_with_history(events)
-
-        if changes:
-            yield changes, doc, rfc_published
+            doc.save_with_history(rfc_events)
+            yield rfc_changes, doc, rfc_published  # yield changes to the RFC
 
 
 def post_approved_draft(url, name):

From 4f9a2316ef388c0a6c729dd0202275b956d3f430 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 14:10:49 -0300
Subject: [PATCH 04/25] style: Run Black and use f-strings / str.format()

---
 ietf/sync/rfceditor.py | 62 ++++++++++++++++++++----------------------
 1 file changed, 30 insertions(+), 32 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 101b33369..5c76100e7 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -411,16 +411,14 @@ def update_docs_from_rfc_index(
 
         # Find the document
         doc, created_rfc = Document.objects.get_or_create(
-            rfc_number=rfc_number,
-            type_id="rfc",
-            defaults={"name": f"rfc{rfc_number}"}
+            rfc_number=rfc_number, type_id="rfc", defaults={"name": f"rfc{rfc_number}"}
         )
         if created_rfc:
             rfc_changes.append(f"created document {prettify_std_name(doc.name)}")
             # Create DocAlias (for consistency until we drop DocAlias altogether)
             alias, _ = DocAlias.objects.get_or_create(name=doc.name)
             alias.docs.add(doc)
-            rfc_changes.append("created alias %s" % prettify_std_name(doc.name))
+            rfc_changes.append(f"created alias {prettify_std_name(doc.name)}")
 
         if draft_name:
             try:
@@ -431,11 +429,11 @@ def update_docs_from_rfc_index(
                 # Ensure the draft is in the "rfc" state and move its files to the archive
                 # if necessary.
                 if draft.get_state_slug() != "rfc":
-                    draft.set_state(State.objects.get(used=True, type="draft", slug="rfc"))
-                    move_draft_files_to_archive(draft, draft.rev)
-                    draft_changes.append(
-                        f"changed state to {draft.get_state()}"
+                    draft.set_state(
+                        State.objects.get(used=True, type="draft", slug="rfc")
                     )
+                    move_draft_files_to_archive(draft, draft.rev)
+                    draft_changes.append(f"changed state to {draft.get_state()}")
 
                 # Ensure the draft and rfc are linked with a "became_rfc" relationship
                 r, created_relateddoc = RelatedDocument.objects.get_or_create(
@@ -464,40 +462,44 @@ def update_docs_from_rfc_index(
                                 used=True, type=t, slug="pub"
                             )
                             draft.set_state(new_state)
-                            draft_changes.append(f"changed {new_state.type.label} to {new_state}")
+                            draft_changes.append(
+                                f"changed {new_state.type.label} to {new_state}"
+                            )
                             e = update_action_holders(draft, prev_state, new_state)
                             if e:
                                 draft_events.append(e)
                     elif t == "draft-iesg":
-                        draft.set_state(State.objects.get(type_id="draft-iesg", slug="idexists"))
+                        draft.set_state(
+                            State.objects.get(type_id="draft-iesg", slug="idexists")
+                        )
 
         # check attributes
         if title != doc.title:
             doc.title = title
-            rfc_changes.append("changed title to '%s'" % doc.title)
+            rfc_changes.append(f"changed title to '{doc.title}'")
 
         if abstract and abstract != doc.abstract:
             doc.abstract = abstract
-            rfc_changes.append("changed abstract to '%s'" % doc.abstract)
+            rfc_changes.append(f"changed abstract to '{doc.abstract}'")
 
         if pages and int(pages) != doc.pages:
             doc.pages = int(pages)
-            rfc_changes.append("changed pages to %s" % doc.pages)
+            rfc_changes.append(f"changed pages to {doc.pages}")
 
         if std_level_mapping[current_status] != doc.std_level:
             doc.std_level = std_level_mapping[current_status]
-            rfc_changes.append("changed standardization level to %s" % doc.std_level)
+            rfc_changes.append(f"changed standardization level to {doc.std_level}")
 
         if doc.stream != stream_mapping[stream]:
             doc.stream = stream_mapping[stream]
-            rfc_changes.append("changed stream to %s" % doc.stream)
+            rfc_changes.append(f"changed stream to {doc.stream}")
 
         if (
             not doc.group
         ):  # if we have no group assigned, check if RFC Editor has a suggestion
             if wg:
                 doc.group = Group.objects.get(acronym=wg)
-                rfc_changes.append("set group to %s" % doc.group)
+                rfc_changes.append(f"set group to {doc.group}")
             else:
                 doc.group = Group.objects.get(
                     type="individ"
@@ -531,7 +533,7 @@ def update_docs_from_rfc_index(
             rfc_events.append(e)
 
             rfc_changes.append(
-                "added RFC published event at %s" % e.time.strftime("%Y-%m-%d")
+                f"added RFC published event at {e.time.strftime('%Y-%m-%d')}"
             )
             rfc_published = True
 
@@ -561,11 +563,10 @@ def update_docs_from_rfc_index(
                     source=doc, target=x, relationship=relationship_obsoletes
                 )
                 rfc_changes.append(
-                    "created %s relation between %s and %s"
-                    % (
-                        r.relationship.name.lower(),
-                        prettify_std_name(r.source.name),
-                        prettify_std_name(r.target.name),
+                    "created {rel_name} relation between {src_name} and {tgt_name}".format(
+                        rel_name=r.relationship.name.lower(),
+                        src_name=prettify_std_name(r.source.name),
+                        tgt_name=prettify_std_name(r.target.name),
                     )
                 )
 
@@ -577,11 +578,10 @@ def update_docs_from_rfc_index(
                     source=doc, target=x, relationship=relationship_updates
                 )
                 rfc_changes.append(
-                    "created %s relation between %s and %s"
-                    % (
-                        r.relationship.name.lower(),
-                        prettify_std_name(r.source.name),
-                        prettify_std_name(r.target.name),
+                    "created {rel_name} relation between {src_name} and {tgt_name}".format(
+                        rel_name=r.relationship.name.lower(),
+                        src_name=prettify_std_name(r.source.name),
+                        tgt_name=prettify_std_name(r.target.name),
                     )
                 )
 
@@ -590,7 +590,7 @@ def update_docs_from_rfc_index(
                 a = a.lower()
                 if not DocAlias.objects.filter(name=a):
                     DocAlias.objects.create(name=a).docs.add(doc)
-                    rfc_changes.append("created alias %s" % prettify_std_name(a))
+                    rfc_changes.append(f"created alias {prettify_std_name(a)}")
 
         doc_errata = errata.get("RFC%04d" % rfc_number, [])  # rfc10k problem here
         all_rejected = doc_errata and all(
@@ -628,8 +628,7 @@ def update_docs_from_rfc_index(
                     rev=doc.rev,
                     by=system,
                     type="sync_from_rfc_editor",
-                    desc="Received changes through RFC Editor sync (%s)"
-                         % ", ".join(draft_changes),
+                    desc=f"Received changes through RFC Editor sync ({', '.join(draft_changes)})",
                 )
             )
             draft.save_with_history(draft_events)
@@ -642,8 +641,7 @@ def update_docs_from_rfc_index(
                     rev=doc.rev,
                     by=system,
                     type="sync_from_rfc_editor",
-                    desc="Received changes through RFC Editor sync (%s)"
-                         % ", ".join(rfc_changes),
+                    desc=f"Received changes through RFC Editor sync ({', '.join(rfc_changes)})",
                 )
             )
             doc.save_with_history(rfc_events)

From c37e3b4a397c7746351c285907952458ed88dedc Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 14:29:02 -0300
Subject: [PATCH 05/25] refactor: Consolidate draft handling

---
 ietf/sync/rfceditor.py | 31 +++++++++++++++----------------
 1 file changed, 15 insertions(+), 16 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 5c76100e7..8e5f782bc 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -406,8 +406,6 @@ def update_docs_from_rfc_index(
         rfc_events = []
         rfc_changes = []
         rfc_published = False
-        draft_events = []
-        draft_changes = []
 
         # Find the document
         doc, created_rfc = Document.objects.get_or_create(
@@ -426,6 +424,9 @@ def update_docs_from_rfc_index(
             except Document.DoesNotExist:
                 pass
             else:
+                draft_events = []
+                draft_changes = []
+
                 # Ensure the draft is in the "rfc" state and move its files to the archive
                 # if necessary.
                 if draft.get_state_slug() != "rfc":
@@ -472,6 +473,18 @@ def update_docs_from_rfc_index(
                         draft.set_state(
                             State.objects.get(type_id="draft-iesg", slug="idexists")
                         )
+                if draft_changes:
+                    draft_events.append(
+                        DocEvent.objects.create(
+                            doc=draft,
+                            rev=doc.rev,
+                            by=system,
+                            type="sync_from_rfc_editor",
+                            desc=f"Received changes through RFC Editor sync ({', '.join(draft_changes)})",
+                        )
+                    )
+                    draft.save_with_history(draft_events)
+                    yield draft_changes, draft, False  # yield changes to the draft
 
         # check attributes
         if title != doc.title:
@@ -620,20 +633,6 @@ def update_docs_from_rfc_index(
                 doc.tags.remove(tag_has_verified_errata)
                 rfc_changes.append("removed Verified Errata tag")
 
-        # Update the draft first
-        if draft_changes:
-            draft_events.append(
-                DocEvent.objects.create(
-                    doc=draft,
-                    rev=doc.rev,
-                    by=system,
-                    type="sync_from_rfc_editor",
-                    desc=f"Received changes through RFC Editor sync ({', '.join(draft_changes)})",
-                )
-            )
-            draft.save_with_history(draft_events)
-            yield draft_changes, draft, False  # yield changes to the draft
-
         if rfc_changes:
             rfc_events.append(
                 DocEvent.objects.create(

From eaf8d459ec7d09cca88cf774a892eccda11f4a6e Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 14:58:32 -0300
Subject: [PATCH 06/25] fix: Only match draft docs to rfc index draft name

---
 ietf/sync/rfceditor.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 8e5f782bc..e82d645c4 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -420,7 +420,7 @@ def update_docs_from_rfc_index(
 
         if draft_name:
             try:
-                draft = Document.objects.get(name=draft_name)
+                draft = Document.objects.get(name=draft_name, type_id="draft")
             except Document.DoesNotExist:
                 pass
             else:

From 542b87209dc1aa324a3196cf56903a169d8f770c Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 14:59:55 -0300
Subject: [PATCH 07/25] chore: Eliminate python lint

---
 ietf/sync/rfceditor.py | 12 ++++++------
 1 file changed, 6 insertions(+), 6 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index e82d645c4..5f6865c98 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -7,7 +7,7 @@ import datetime
 import re
 import requests
 
-from typing import Iterator, Optional
+from typing import Iterator
 from urllib.parse import urlencode
 from xml.dom import pulldom, Node
 
@@ -18,7 +18,7 @@ from django.utils.encoding import smart_bytes, force_str
 import debug                            # pyflakes:ignore
 
 from ietf.doc.models import ( Document, DocAlias, State, StateType, DocEvent, DocRelationshipName,
-    DocTagName, DocTypeName, RelatedDocument )
+    DocTagName, RelatedDocument )
 from ietf.doc.expire import move_draft_files_to_archive
 from ietf.doc.utils import add_state_change_event, prettify_std_name, update_action_holders
 from ietf.group.models import Group
@@ -342,8 +342,8 @@ def update_docs_from_rfc_index(
 
     The skip_older_than_date is a bare date, not a datetime.
     """
-
-    errata = {}
+    # Create dict mapping doc-id to list of errata records that apply to it
+    errata: dict[str, list[dict]] = {}
     for item in errata_data:
         name = item["doc-id"]
         if not name in errata:
@@ -443,8 +443,8 @@ def update_docs_from_rfc_index(
                 if created_relateddoc:
                     change = "created {rel_name} relationship between {pretty_draft_name} and {pretty_rfc_name}".format(
                         rel_name=r.relationship.name.lower(),
-                        pretty_draft_name=prettify_std_name(draft),
-                        pretty_rfc_name=prettify_std_name(doc),
+                        pretty_draft_name=prettify_std_name(draft_name),
+                        pretty_rfc_name=prettify_std_name(doc.name),
                     )
                     draft_changes.append(change)
                     rfc_changes.append(change)

From 4437796bc30eb42fe37c34a2fffaf6229c83f196 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 15:43:10 -0300
Subject: [PATCH 08/25] test: Update test_rfc_index()

---
 ietf/sync/tests.py | 75 +++++++++++++++++++++++++++-------------------
 1 file changed, 45 insertions(+), 30 deletions(-)

diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py
index 4ad26e3e4..8ca6362c7 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -226,14 +226,14 @@ class RFCSyncTests(TestCase):
 
     def test_rfc_index(self):
         area = GroupFactory(type_id='area')
-        doc = WgDraftFactory(
+        draft_doc = WgDraftFactory(
             group__parent=area,
             states=[('draft-iesg','rfcqueue'),('draft-stream-ise','rfc-edit')],
             ad=Person.objects.get(user__username='ad'),
         )
         # it's a bit strange to have draft-stream-ise set when draft-iesg is set
         # too, but for testing purposes ...
-        doc.action_holders.add(doc.ad)  # not normally set, but add to be sure it's cleared
+        draft_doc.action_holders.add(draft_doc.ad)  # not normally set, but add to be sure it's cleared
 
         RfcFactory(rfc_number=123)
 
@@ -298,10 +298,10 @@ class RFCSyncTests(TestCase):
     </rfc-entry>
 </rfc-index>''' % dict(year=today.strftime("%Y"),
                        month=today.strftime("%B"),
-                       name=doc.name,
-                       rev=doc.rev,
-                       area=doc.group.parent.acronym,
-                       group=doc.group.acronym)
+                       name=draft_doc.name,
+                       rev=draft_doc.rev,
+                       area=draft_doc.group.parent.acronym,
+                       group=draft_doc.group.acronym)
 
         errata = [{
                 "errata_id":1,
@@ -321,7 +321,7 @@ class RFCSyncTests(TestCase):
 
         data = rfceditor.parse_index(io.StringIO(t))
         self.assertEqual(len(data), 1)
-
+        
         rfc_number, title, authors, rfc_published_date, current_status, updates, updated_by, obsoletes, obsoleted_by, also, draft, has_errata, stream, wg, file_formats, pages, abstract = data[0]
 
         # currently, we only check what we actually use
@@ -332,44 +332,59 @@ class RFCSyncTests(TestCase):
         self.assertEqual(current_status, "Proposed Standard")
         self.assertEqual(updates, ["RFC123"])
         self.assertEqual(set(also), set(["BCP1", "FYI1", "STD1"]))
-        self.assertEqual(draft, doc.name)
-        self.assertEqual(wg, doc.group.acronym)
+        self.assertEqual(draft, draft_doc.name)
+        self.assertEqual(wg, draft_doc.group.acronym)
         self.assertEqual(has_errata, True)
         self.assertEqual(stream, "IETF")
         self.assertEqual(pages, "42")
         self.assertEqual(abstract, "This is some interesting text.")
 
-        draft_filename = "%s-%s.txt" % (doc.name, doc.rev)
+        draft_filename = "%s-%s.txt" % (draft_doc.name, draft_doc.rev)
         self.write_draft_file(draft_filename, 5000)
 
+        event_count_before = draft_doc.docevent_set.count()
+        draft_title_before = draft_doc.title
+        draft_abstract_before = draft_doc.abstract
+        draft_pages_before = draft_doc.pages
         changes = []
         for cs, d, rfc_published in rfceditor.update_docs_from_rfc_index(data, errata, today - datetime.timedelta(days=30)):
             changes.append(cs)
 
-        doc = Document.objects.get(name=doc.name)
+        draft_doc = Document.objects.get(name=draft_doc.name)
 
-        events = doc.docevent_set.all()
-        self.assertEqual(events[0].type, "sync_from_rfc_editor")
-        self.assertEqual(events[1].type, "changed_action_holders")
-        self.assertEqual(events[2].type, "published_rfc")
-        self.assertEqual(events[2].time.astimezone(RPC_TZINFO).date(), today)
-        self.assertTrue("errata" in doc.tags.all().values_list("slug", flat=True))
-        self.assertTrue(DocAlias.objects.filter(name="rfc1234", docs=doc))
-        self.assertTrue(DocAlias.objects.filter(name="bcp1", docs=doc))
-        self.assertTrue(DocAlias.objects.filter(name="fyi1", docs=doc))
-        self.assertTrue(DocAlias.objects.filter(name="std1", docs=doc))
-        self.assertTrue(RelatedDocument.objects.filter(source=doc, target__name="rfc123", relationship="updates").exists())
-        self.assertEqual(doc.title, "A Testing RFC")
-        self.assertEqual(doc.abstract, "This is some interesting text.")
-        self.assertEqual(doc.get_state_slug(), "rfc")
-        self.assertEqual(doc.get_state_slug("draft-iesg"), "pub")
-        self.assertCountEqual(doc.action_holders.all(), [])
-        self.assertEqual(doc.get_state_slug("draft-stream-ise"), "pub")
-        self.assertEqual(doc.std_level_id, "ps")
-        self.assertEqual(doc.pages, 42)
+        draft_events = draft_doc.docevent_set.all()
+        self.assertEqual(len(draft_events) - event_count_before, 2)
+        self.assertEqual(draft_events[0].type, "sync_from_rfc_editor")
+        self.assertEqual(draft_events[1].type, "changed_action_holders")
+        self.assertEqual(draft_doc.get_state_slug(), "rfc")
+        self.assertEqual(draft_doc.get_state_slug("draft-iesg"), "pub")
+        self.assertCountEqual(draft_doc.action_holders.all(), [])
+        self.assertEqual(draft_doc.get_state_slug("draft-stream-ise"), "pub")
+        self.assertEqual(draft_doc.title, draft_title_before)
+        self.assertEqual(draft_doc.abstract, draft_abstract_before)
+        self.assertEqual(draft_doc.pages, draft_pages_before)
         self.assertTrue(not os.path.exists(os.path.join(settings.INTERNET_DRAFT_PATH, draft_filename)))
         self.assertTrue(os.path.exists(os.path.join(settings.INTERNET_DRAFT_ARCHIVE_DIR, draft_filename)))
 
+        rfc_doc = Document.objects.filter(rfc_number=1234, type_id="rfc").first()
+        self.assertIsNotNone(rfc_doc, "RFC document should have been created")
+        rfc_events = rfc_doc.docevent_set.all()
+        self.assertEqual(len(rfc_events), 2)
+        self.assertEqual(rfc_events[0].type, "sync_from_rfc_editor")
+        self.assertEqual(rfc_events[1].type, "published_rfc")
+        self.assertEqual(rfc_events[1].time.astimezone(RPC_TZINFO).date(), today)
+        self.assertTrue("errata" in rfc_doc.tags.all().values_list("slug", flat=True))
+        self.assertTrue(DocAlias.objects.filter(name="rfc1234", docs=rfc_doc))
+        self.assertTrue(DocAlias.objects.filter(name="bcp1", docs=rfc_doc))
+        self.assertTrue(DocAlias.objects.filter(name="fyi1", docs=rfc_doc))
+        self.assertTrue(DocAlias.objects.filter(name="std1", docs=rfc_doc))
+        self.assertTrue(RelatedDocument.objects.filter(source=rfc_doc, target__name="rfc123", relationship="updates").exists())
+        self.assertTrue(RelatedDocument.objects.filter(source=draft_doc, target=rfc_doc, relationship="became_rfc").exists())
+        self.assertEqual(rfc_doc.title, "A Testing RFC")
+        self.assertEqual(rfc_doc.abstract, "This is some interesting text.")
+        self.assertEqual(rfc_doc.std_level_id, "ps")
+        self.assertEqual(rfc_doc.pages, 42)
+
         # make sure we can apply it again with no changes
         changed = list(rfceditor.update_docs_from_rfc_index(data, errata, today - datetime.timedelta(days=30)))
         self.assertEqual(len(changed), 0)

From 112ac906dcae2c2af798750b7b993f6e1b2fac22 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 16:01:34 -0300
Subject: [PATCH 09/25] test: Confirm changes are returned for draft and rfc

---
 ietf/sync/tests.py | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py
index 8ca6362c7..345bf6eb6 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -347,11 +347,10 @@ class RFCSyncTests(TestCase):
         draft_abstract_before = draft_doc.abstract
         draft_pages_before = draft_doc.pages
         changes = []
-        for cs, d, rfc_published in rfceditor.update_docs_from_rfc_index(data, errata, today - datetime.timedelta(days=30)):
-            changes.append(cs)
+        for _, d, rfc_published in rfceditor.update_docs_from_rfc_index(data, errata, today - datetime.timedelta(days=30)):
+            changes.append({"doc_pk": d.pk, "rfc_published": rfc_published})  # we ignore the actual change list
 
         draft_doc = Document.objects.get(name=draft_doc.name)
-
         draft_events = draft_doc.docevent_set.all()
         self.assertEqual(len(draft_events) - event_count_before, 2)
         self.assertEqual(draft_events[0].type, "sync_from_rfc_editor")
@@ -385,6 +384,13 @@ class RFCSyncTests(TestCase):
         self.assertEqual(rfc_doc.std_level_id, "ps")
         self.assertEqual(rfc_doc.pages, 42)
 
+        # check that we got the expected changes
+        self.assertEqual(len(changes), 2)
+        self.assertEqual(changes[0]["doc_pk"], draft_doc.pk)
+        self.assertEqual(changes[0]["rfc_published"], False)
+        self.assertEqual(changes[1]["doc_pk"], rfc_doc.pk)
+        self.assertEqual(changes[1]["rfc_published"], True)
+
         # make sure we can apply it again with no changes
         changed = list(rfceditor.update_docs_from_rfc_index(data, errata, today - datetime.timedelta(days=30)))
         self.assertEqual(len(changed), 0)

From e898e43bc809642d8977497858c183f3fc622df3 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 16:09:53 -0300
Subject: [PATCH 10/25] fix: Only consider rfcs for updates/obsoletes targets

---
 ietf/sync/rfceditor.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 5f6865c98..e7e500696 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -561,7 +561,7 @@ def update_docs_from_rfc_index(
                 #    # sensibly; otherwise we'll have to ignore them
                 #   l = DocAlias.objects.filter(name__startswith="rfc", docs__docalias__name=x.lower())
                 # else:
-                l = Document.objects.filter(name=x.lower())
+                l = Document.objects.filter(name=x.lower(), type_id="rfc")
 
                 for a in l:
                     if a not in res:

From fd1d425db6687fd5e2b657d877630c7efd8a903e Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 16:12:01 -0300
Subject: [PATCH 11/25] feat: Log warning if rfc index refers to unknown draft

---
 ietf/sync/rfceditor.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index e7e500696..b2e8a15c2 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -422,7 +422,7 @@ def update_docs_from_rfc_index(
             try:
                 draft = Document.objects.get(name=draft_name, type_id="draft")
             except Document.DoesNotExist:
-                pass
+                log(f"Warning: RFC index for {rfc_number} referred to unknown draft {draft_name}")
             else:
                 draft_events = []
                 draft_changes = []

From 7535df3eeb67dddcb9756f17761828e7d98c27bf Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 17:00:08 -0300
Subject: [PATCH 12/25] feat: Set more fields on rfc doc

---
 ietf/sync/rfceditor.py | 149 ++++++++++++++++++++++++-----------------
 1 file changed, 86 insertions(+), 63 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index b2e8a15c2..7f668c496 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -374,6 +374,7 @@ def update_docs_from_rfc_index(
     tag_has_verified_errata = DocTagName.objects.get(slug="verified-errata")
     relationship_obsoletes = DocRelationshipName.objects.get(slug="obs")
     relationship_updates = DocRelationshipName.objects.get(slug="updates")
+    rfc_published_state = State.objects.get(type_id="rfc", slug="published")
 
     system = Person.objects.get(name="(System)")
 
@@ -407,9 +408,34 @@ def update_docs_from_rfc_index(
         rfc_changes = []
         rfc_published = False
 
-        # Find the document
+        # Find the draft, if any
+        draft = None
+        if draft_name:
+            try:
+                draft = Document.objects.get(name=draft_name, type_id="draft")
+            except Document.DoesNotExist:
+                log(f"Warning: RFC index for {rfc_number} referred to unknown draft {draft_name}")
+
+        # Find or create the RFC document
+        creation_args = {"name": f"rfc{rfc_number}"}
+        if draft:
+            creation_args.update(
+                {
+                    "title": draft.title,
+                    "stream": draft.stream,
+                    "group": draft.group,
+                    "abstract": draft.abstract,
+                    "pages": draft.pages,
+                    "words": draft.words,
+                    "std_level": draft.std_level,
+                    "ad": draft.ad,
+                    "external_url": draft.external_url,
+                    "uploaded_filename": draft.uploaded_filename,
+                    "note": draft.note,
+                }
+            )
         doc, created_rfc = Document.objects.get_or_create(
-            rfc_number=rfc_number, type_id="rfc", defaults={"name": f"rfc{rfc_number}"}
+            rfc_number=rfc_number, type_id="rfc", defaults=creation_args
         )
         if created_rfc:
             rfc_changes.append(f"created document {prettify_std_name(doc.name)}")
@@ -417,74 +443,71 @@ def update_docs_from_rfc_index(
             alias, _ = DocAlias.objects.get_or_create(name=doc.name)
             alias.docs.add(doc)
             rfc_changes.append(f"created alias {prettify_std_name(doc.name)}")
+            if draft:
+                doc.formal_languages.set(draft.formal_languages.all())
 
-        if draft_name:
-            try:
-                draft = Document.objects.get(name=draft_name, type_id="draft")
-            except Document.DoesNotExist:
-                log(f"Warning: RFC index for {rfc_number} referred to unknown draft {draft_name}")
-            else:
-                draft_events = []
-                draft_changes = []
+        if draft:
+            draft_events = []
+            draft_changes = []
 
-                # Ensure the draft is in the "rfc" state and move its files to the archive
-                # if necessary.
-                if draft.get_state_slug() != "rfc":
-                    draft.set_state(
-                        State.objects.get(used=True, type="draft", slug="rfc")
-                    )
-                    move_draft_files_to_archive(draft, draft.rev)
-                    draft_changes.append(f"changed state to {draft.get_state()}")
-
-                # Ensure the draft and rfc are linked with a "became_rfc" relationship
-                r, created_relateddoc = RelatedDocument.objects.get_or_create(
-                    source=draft, target=doc, relationship_id="became_rfc"
+            # Ensure the draft is in the "rfc" state and move its files to the archive
+            # if necessary.
+            if draft.get_state_slug() != "rfc":
+                draft.set_state(
+                    State.objects.get(used=True, type="draft", slug="rfc")
                 )
-                if created_relateddoc:
-                    change = "created {rel_name} relationship between {pretty_draft_name} and {pretty_rfc_name}".format(
-                        rel_name=r.relationship.name.lower(),
-                        pretty_draft_name=prettify_std_name(draft_name),
-                        pretty_rfc_name=prettify_std_name(doc.name),
-                    )
-                    draft_changes.append(change)
-                    rfc_changes.append(change)
+                move_draft_files_to_archive(draft, draft.rev)
+                draft_changes.append(f"changed state to {draft.get_state()}")
 
-                # Ensure draft is in the correct iesg and stream states
-                for t in (
-                    "draft-iesg",
-                    "draft-stream-iab",
-                    "draft-stream-irtf",
-                    "draft-stream-ise",
-                ):
-                    prev_state = draft.get_state(t)
-                    if prev_state is not None:
-                        if prev_state.slug not in ("pub", "idexists"):
-                            new_state = State.objects.select_related("type").get(
-                                used=True, type=t, slug="pub"
-                            )
-                            draft.set_state(new_state)
-                            draft_changes.append(
-                                f"changed {new_state.type.label} to {new_state}"
-                            )
-                            e = update_action_holders(draft, prev_state, new_state)
-                            if e:
-                                draft_events.append(e)
-                    elif t == "draft-iesg":
-                        draft.set_state(
-                            State.objects.get(type_id="draft-iesg", slug="idexists")
+            # Ensure the draft and rfc are linked with a "became_rfc" relationship
+            r, created_relateddoc = RelatedDocument.objects.get_or_create(
+                source=draft, target=doc, relationship_id="became_rfc"
+            )
+            if created_relateddoc:
+                change = "created {rel_name} relationship between {pretty_draft_name} and {pretty_rfc_name}".format(
+                    rel_name=r.relationship.name.lower(),
+                    pretty_draft_name=prettify_std_name(draft_name),
+                    pretty_rfc_name=prettify_std_name(doc.name),
+                )
+                draft_changes.append(change)
+                rfc_changes.append(change)
+
+            # Ensure draft is in the correct iesg and stream states
+            for t in (
+                "draft-iesg",
+                "draft-stream-iab",
+                "draft-stream-irtf",
+                "draft-stream-ise",
+            ):
+                prev_state = draft.get_state(t)
+                if prev_state is not None:
+                    if prev_state.slug not in ("pub", "idexists"):
+                        new_state = State.objects.select_related("type").get(
+                            used=True, type=t, slug="pub"
                         )
-                if draft_changes:
-                    draft_events.append(
-                        DocEvent.objects.create(
-                            doc=draft,
-                            rev=doc.rev,
-                            by=system,
-                            type="sync_from_rfc_editor",
-                            desc=f"Received changes through RFC Editor sync ({', '.join(draft_changes)})",
+                        draft.set_state(new_state)
+                        draft_changes.append(
+                            f"changed {new_state.type.label} to {new_state}"
                         )
+                        e = update_action_holders(draft, prev_state, new_state)
+                        if e:
+                            draft_events.append(e)
+                elif t == "draft-iesg":
+                    draft.set_state(
+                        State.objects.get(type_id="draft-iesg", slug="idexists")
                     )
-                    draft.save_with_history(draft_events)
-                    yield draft_changes, draft, False  # yield changes to the draft
+            if draft_changes:
+                draft_events.append(
+                    DocEvent.objects.create(
+                        doc=draft,
+                        rev=doc.rev,
+                        by=system,
+                        type="sync_from_rfc_editor",
+                        desc=f"Received changes through RFC Editor sync ({', '.join(draft_changes)})",
+                    )
+                )
+                draft.save_with_history(draft_events)
+                yield draft_changes, draft, False  # yield changes to the draft
 
         # check attributes
         if title != doc.title:

From 39ccea92f9c6e243ce560f1b9c3f0d1dc965f7cb Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 17:01:06 -0300
Subject: [PATCH 13/25] feat: Set published state on rfc doc

---
 ietf/sync/rfceditor.py | 10 +++++++---
 1 file changed, 7 insertions(+), 3 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 7f668c496..8b0fc5f5e 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -443,6 +443,7 @@ def update_docs_from_rfc_index(
             alias, _ = DocAlias.objects.get_or_create(name=doc.name)
             alias.docs.add(doc)
             rfc_changes.append(f"created alias {prettify_std_name(doc.name)}")
+            doc.set_state(rfc_published_state)
             if draft:
                 doc.formal_languages.set(draft.formal_languages.all())
 
@@ -530,9 +531,12 @@ def update_docs_from_rfc_index(
             doc.stream = stream_mapping[stream]
             rfc_changes.append(f"changed stream to {doc.stream}")
 
-        if (
-            not doc.group
-        ):  # if we have no group assigned, check if RFC Editor has a suggestion
+        if doc.get_state(rfc_published_state.type) != rfc_published_state:
+            doc.set_state(rfc_published_state)
+            rfc_changes.append(f"changed {rfc_published_state.type.label} to {rfc_published_state}")
+
+        # if we have no group assigned, check if RFC Editor has a suggestion
+        if not doc.group:  
             if wg:
                 doc.group = Group.objects.get(acronym=wg)
                 rfc_changes.append(f"set group to {doc.group}")

From 8b636216d0b34ba5578e6e90996ffd15b168cc09 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 17:35:12 -0300
Subject: [PATCH 14/25] feat: describe change as "set" or "changed" as
 appropriate

---
 ietf/sync/rfceditor.py | 13 +++++++------
 1 file changed, 7 insertions(+), 6 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 8b0fc5f5e..d924fb522 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -511,29 +511,30 @@ def update_docs_from_rfc_index(
                 yield draft_changes, draft, False  # yield changes to the draft
 
         # check attributes
+        verbed = "set" if created_rfc else "changed"
         if title != doc.title:
             doc.title = title
-            rfc_changes.append(f"changed title to '{doc.title}'")
+            rfc_changes.append(f"{verbed} title to '{doc.title}'")
 
         if abstract and abstract != doc.abstract:
             doc.abstract = abstract
-            rfc_changes.append(f"changed abstract to '{doc.abstract}'")
+            rfc_changes.append(f"{verbed} abstract to '{doc.abstract}'")
 
         if pages and int(pages) != doc.pages:
             doc.pages = int(pages)
-            rfc_changes.append(f"changed pages to {doc.pages}")
+            rfc_changes.append(f"{verbed} pages to {doc.pages}")
 
         if std_level_mapping[current_status] != doc.std_level:
             doc.std_level = std_level_mapping[current_status]
-            rfc_changes.append(f"changed standardization level to {doc.std_level}")
+            rfc_changes.append(f"{verbed} standardization level to {doc.std_level}")
 
         if doc.stream != stream_mapping[stream]:
             doc.stream = stream_mapping[stream]
-            rfc_changes.append(f"changed stream to {doc.stream}")
+            rfc_changes.append(f"{verbed} stream to {doc.stream}")
 
         if doc.get_state(rfc_published_state.type) != rfc_published_state:
             doc.set_state(rfc_published_state)
-            rfc_changes.append(f"changed {rfc_published_state.type.label} to {rfc_published_state}")
+            rfc_changes.append(f"{verbed} {rfc_published_state.type.label} to {rfc_published_state}")
 
         # if we have no group assigned, check if RFC Editor has a suggestion
         if not doc.group:  

From ab99972d768ec3b30ef04812c847149208fb3157 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 17:44:37 -0300
Subject: [PATCH 15/25] fix: Check state correctly

---
 ietf/sync/rfceditor.py | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index d924fb522..a88e420f6 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -532,7 +532,7 @@ def update_docs_from_rfc_index(
             doc.stream = stream_mapping[stream]
             rfc_changes.append(f"{verbed} stream to {doc.stream}")
 
-        if doc.get_state(rfc_published_state.type) != rfc_published_state:
+        if doc.get_state() != rfc_published_state:
             doc.set_state(rfc_published_state)
             rfc_changes.append(f"{verbed} {rfc_published_state.type.label} to {rfc_published_state}")
 

From 92ad44e70ca51eb828773970f4ec96f626d85b26 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 17:53:35 -0300
Subject: [PATCH 16/25] test: Test more fields on the rfc doc

---
 ietf/sync/tests.py | 9 +++++++++
 1 file changed, 9 insertions(+)

diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py
index 345bf6eb6..2706c7057 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -230,6 +230,8 @@ class RFCSyncTests(TestCase):
             group__parent=area,
             states=[('draft-iesg','rfcqueue'),('draft-stream-ise','rfc-edit')],
             ad=Person.objects.get(user__username='ad'),
+            external_url="http://my-external-url.example.com",
+            note="this is a note",
         )
         # it's a bit strange to have draft-stream-ise set when draft-iesg is set
         # too, but for testing purposes ...
@@ -372,6 +374,7 @@ class RFCSyncTests(TestCase):
         self.assertEqual(rfc_events[0].type, "sync_from_rfc_editor")
         self.assertEqual(rfc_events[1].type, "published_rfc")
         self.assertEqual(rfc_events[1].time.astimezone(RPC_TZINFO).date(), today)
+        self.assertEqual(rfc_doc.get_state_slug(), "published")
         self.assertTrue("errata" in rfc_doc.tags.all().values_list("slug", flat=True))
         self.assertTrue(DocAlias.objects.filter(name="rfc1234", docs=rfc_doc))
         self.assertTrue(DocAlias.objects.filter(name="bcp1", docs=rfc_doc))
@@ -383,6 +386,12 @@ class RFCSyncTests(TestCase):
         self.assertEqual(rfc_doc.abstract, "This is some interesting text.")
         self.assertEqual(rfc_doc.std_level_id, "ps")
         self.assertEqual(rfc_doc.pages, 42)
+        self.assertEqual(rfc_doc.stream, draft_doc.stream)
+        self.assertEqual(rfc_doc.group, draft_doc.group)
+        self.assertEqual(rfc_doc.words, draft_doc.words)
+        self.assertEqual(rfc_doc.ad, draft_doc.ad)
+        self.assertEqual(rfc_doc.external_url, draft_doc.external_url)
+        self.assertEqual(rfc_doc.note, draft_doc.note)
 
         # check that we got the expected changes
         self.assertEqual(len(changes), 2)

From a66457bfaf2fe67f23397a2288be471ac23d5f16 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Thu, 10 Aug 2023 18:01:11 -0300
Subject: [PATCH 17/25] test: Remove python lint

---
 ietf/sync/rfceditor.py | 4 ++--
 1 file changed, 2 insertions(+), 2 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index a88e420f6..1e3e0bf73 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -7,7 +7,7 @@ import datetime
 import re
 import requests
 
-from typing import Iterator
+from typing import Iterator, Optional, Union
 from urllib.parse import urlencode
 from xml.dom import pulldom, Node
 
@@ -417,7 +417,7 @@ def update_docs_from_rfc_index(
                 log(f"Warning: RFC index for {rfc_number} referred to unknown draft {draft_name}")
 
         # Find or create the RFC document
-        creation_args = {"name": f"rfc{rfc_number}"}
+        creation_args: dict[str, Optional[Union[str, int]]] = {"name": f"rfc{rfc_number}"}
         if draft:
             creation_args.update(
                 {

From aff4c2d0db6aecdf3ccb8c8e1124167974262bef Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Fri, 11 Aug 2023 12:04:15 -0300
Subject: [PATCH 18/25] fix: Drop leading 0s from RFC doc-id

---
 ietf/sync/rfceditor.py | 4 +++-
 1 file changed, 3 insertions(+), 1 deletion(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 1e3e0bf73..56501cb0f 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -346,6 +346,8 @@ def update_docs_from_rfc_index(
     errata: dict[str, list[dict]] = {}
     for item in errata_data:
         name = item["doc-id"]
+        if name.upper().startswith("RFC"):
+            name = f"RFC{int(name[3:])}"  # removes leading 0s on the rfc number
         if not name in errata:
             errata[name] = []
         errata[name].append(item)
@@ -633,7 +635,7 @@ def update_docs_from_rfc_index(
                     DocAlias.objects.create(name=a).docs.add(doc)
                     rfc_changes.append(f"created alias {prettify_std_name(a)}")
 
-        doc_errata = errata.get("RFC%04d" % rfc_number, [])  # rfc10k problem here
+        doc_errata = errata.get(f"RFC{rfc_number}", [])
         all_rejected = doc_errata and all(
             er["errata_status_code"] == "Rejected" for er in doc_errata
         )

From f9ca1bc9d615ce8003fc663061ce303c94c3475b Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Mon, 14 Aug 2023 12:15:09 -0300
Subject: [PATCH 19/25] test: Test behavior of verified-errata tag

Also add comments flagging the near-miss between doc-id
and RFC number in the test_rfc_index() test data. It's
unclear whether these were intended to match, but the
test was self-consistent so I am not changing it.
---
 ietf/sync/tests.py | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py
index 2706c7057..e1ea1522e 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -307,7 +307,7 @@ class RFCSyncTests(TestCase):
 
         errata = [{
                 "errata_id":1,
-                "doc-id":"RFC123",
+                "doc-id":"RFC123",  # n.b. this is not the same RFC as in the above index XML!
                 "errata_status_code":"Verified",
                 "errata_type_code":"Editorial",
                 "section": "4.1",
@@ -375,7 +375,11 @@ class RFCSyncTests(TestCase):
         self.assertEqual(rfc_events[1].type, "published_rfc")
         self.assertEqual(rfc_events[1].time.astimezone(RPC_TZINFO).date(), today)
         self.assertEqual(rfc_doc.get_state_slug(), "published")
-        self.assertTrue("errata" in rfc_doc.tags.all().values_list("slug", flat=True))
+        # Should have an "errata" tag because there is an errata-url in the index XML, but no "verified-errata" tag
+        # because there is no verified item in the errata JSON with doc-id matching the RFC document.
+        tag_slugs = rfc_doc.tags.values_list("slug", flat=True)
+        self.assertTrue("errata" in tag_slugs)
+        self.assertFalse("verified-errata" in tag_slugs)
         self.assertTrue(DocAlias.objects.filter(name="rfc1234", docs=rfc_doc))
         self.assertTrue(DocAlias.objects.filter(name="bcp1", docs=rfc_doc))
         self.assertTrue(DocAlias.objects.filter(name="fyi1", docs=rfc_doc))

From 92460319d141d8a210c114f4ecc85a50e7ff8e23 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Mon, 14 Aug 2023 13:06:48 -0300
Subject: [PATCH 20/25] refactor: Refactor handling of draft stream states

---
 ietf/sync/rfceditor.py | 52 +++++++++++++++++++++++-------------------
 1 file changed, 29 insertions(+), 23 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 56501cb0f..f49ce4e96 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -475,30 +475,36 @@ def update_docs_from_rfc_index(
                 draft_changes.append(change)
                 rfc_changes.append(change)
 
-            # Ensure draft is in the correct iesg and stream states
-            for t in (
-                "draft-iesg",
-                "draft-stream-iab",
-                "draft-stream-irtf",
-                "draft-stream-ise",
-            ):
-                prev_state = draft.get_state(t)
-                if prev_state is not None:
-                    if prev_state.slug not in ("pub", "idexists"):
-                        new_state = State.objects.select_related("type").get(
-                            used=True, type=t, slug="pub"
-                        )
-                        draft.set_state(new_state)
-                        draft_changes.append(
-                            f"changed {new_state.type.label} to {new_state}"
-                        )
-                        e = update_action_holders(draft, prev_state, new_state)
-                        if e:
-                            draft_events.append(e)
-                elif t == "draft-iesg":
-                    draft.set_state(
-                        State.objects.get(type_id="draft-iesg", slug="idexists")
+            # Draft should be in the "pub" draft-iesg state - complain otherwise
+            iesg_state = draft.get_state("draft-iesg")
+            if iesg_state is None:
+                log(f'Warning while processing {doc.name}: {draft.name} has no "draft-iesg" state')
+            elif iesg_state.slug != "pub":
+                log(
+                    'Warning while processing {}: {} is in "draft-iesg" state {} (expected "pub")'.format(
+                        doc.name, draft.name, iesg_state.slug
                     )
+                )
+
+            # If the draft and RFC streams agree, move draft to "pub" stream state. If not, complain.
+            if draft.stream != doc.stream:
+                log("Warning while processing {}: draft {} stream is {} but RFC stream is {}".format(
+                    doc.name, draft.name, draft.stream, doc.stream
+                ))
+            elif draft.stream.slug in ["draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"]:
+                stream_slug = draft.stream.slug
+                prev_state = draft.get_state(stream_slug)
+                if prev_state is None:
+                    log(f"Warning while processing {doc.name}: draft {draft.name} stream state was not set")
+                elif prev_state.slug != "pub":
+                    new_state = State.objects.select_related("type").get(used=True, type__slug=stream_slug, slug="pub")
+                    draft.set_state(new_state)
+                    draft_changes.append(
+                        f"changed {new_state.type.label} to {new_state}"
+                    )
+                    e = update_action_holders(draft, prev_state, new_state)
+                    if e:
+                        draft_events.append(e)
             if draft_changes:
                 draft_events.append(
                     DocEvent.objects.create(

From 06adb25e43c375c89f9839962d41fc5614300076 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Mon, 14 Aug 2023 13:37:24 -0300
Subject: [PATCH 21/25] fix: Set draft-iesg state to "pub" when a draft is
 published

---
 ietf/sync/rfceditor.py | 23 +++++++++++++++--------
 1 file changed, 15 insertions(+), 8 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index f49ce4e96..08ae16f33 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -475,16 +475,23 @@ def update_docs_from_rfc_index(
                 draft_changes.append(change)
                 rfc_changes.append(change)
 
-            # Draft should be in the "pub" draft-iesg state - complain otherwise
-            iesg_state = draft.get_state("draft-iesg")
-            if iesg_state is None:
+            # Draft should be in the "pub" or "approved" draft-iesg state - complain otherwise
+            prev_iesg_state = draft.get_state("draft-iesg")
+            if prev_iesg_state is None:
                 log(f'Warning while processing {doc.name}: {draft.name} has no "draft-iesg" state')
-            elif iesg_state.slug != "pub":
-                log(
-                    'Warning while processing {}: {} is in "draft-iesg" state {} (expected "pub")'.format(
-                        doc.name, draft.name, iesg_state.slug
+            elif prev_iesg_state.slug != "pub":
+                if prev_iesg_state.slug != "rfcqueue":
+                    log(
+                        'Warning while processing {}: {} is in "draft-iesg" state {} (expected "rfcqueue")'.format(
+                            doc.name, draft.name, prev_iesg_state.slug
+                        )
                     )
-                )
+                new_iesg_state = State.objects.get(type_id="draft-iesg", slug="pub")
+                draft.set_state(new_iesg_state)
+                draft_changes.append(f"changed {new_iesg_state.type.label} to {new_iesg_state}")
+                e = update_action_holders(draft, prev_iesg_state, new_iesg_state)
+                if e:
+                    draft_events.append(e)
 
             # If the draft and RFC streams agree, move draft to "pub" stream state. If not, complain.
             if draft.stream != doc.stream:

From cf470a46806c3c99cdd638ea4b9e34b420744e31 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Mon, 14 Aug 2023 14:08:48 -0300
Subject: [PATCH 22/25] fix: Use correct stream slugs

---
 ietf/sync/rfceditor.py | 6 +++---
 1 file changed, 3 insertions(+), 3 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 08ae16f33..2e6861959 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -498,12 +498,12 @@ def update_docs_from_rfc_index(
                 log("Warning while processing {}: draft {} stream is {} but RFC stream is {}".format(
                     doc.name, draft.name, draft.stream, doc.stream
                 ))
-            elif draft.stream.slug in ["draft-stream-iab", "draft-stream-irtf", "draft-stream-ise"]:
-                stream_slug = draft.stream.slug
+            elif draft.stream.slug in ["iab", "irtf", "ise"]:
+                stream_slug = f"draft-stream-{draft.stream.slug}"
                 prev_state = draft.get_state(stream_slug)
                 if prev_state is None:
                     log(f"Warning while processing {doc.name}: draft {draft.name} stream state was not set")
-                elif prev_state.slug != "pub":
+                if prev_state.slug != "pub":
                     new_state = State.objects.select_related("type").get(used=True, type__slug=stream_slug, slug="pub")
                     draft.set_state(new_state)
                     draft_changes.append(

From b565f104d854089bc8147eeea36f0a6af5fd5baf Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Mon, 14 Aug 2023 14:09:37 -0300
Subject: [PATCH 23/25] test: Don't mix stream states in test_rfc_index()

Make the test a better simulation of actual practice.
---
 ietf/sync/tests.py | 5 +----
 1 file changed, 1 insertion(+), 4 deletions(-)

diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py
index e1ea1522e..d5e380672 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -228,13 +228,11 @@ class RFCSyncTests(TestCase):
         area = GroupFactory(type_id='area')
         draft_doc = WgDraftFactory(
             group__parent=area,
-            states=[('draft-iesg','rfcqueue'),('draft-stream-ise','rfc-edit')],
+            states=[('draft-iesg','rfcqueue')],
             ad=Person.objects.get(user__username='ad'),
             external_url="http://my-external-url.example.com",
             note="this is a note",
         )
-        # it's a bit strange to have draft-stream-ise set when draft-iesg is set
-        # too, but for testing purposes ...
         draft_doc.action_holders.add(draft_doc.ad)  # not normally set, but add to be sure it's cleared
 
         RfcFactory(rfc_number=123)
@@ -360,7 +358,6 @@ class RFCSyncTests(TestCase):
         self.assertEqual(draft_doc.get_state_slug(), "rfc")
         self.assertEqual(draft_doc.get_state_slug("draft-iesg"), "pub")
         self.assertCountEqual(draft_doc.action_holders.all(), [])
-        self.assertEqual(draft_doc.get_state_slug("draft-stream-ise"), "pub")
         self.assertEqual(draft_doc.title, draft_title_before)
         self.assertEqual(draft_doc.abstract, draft_abstract_before)
         self.assertEqual(draft_doc.pages, draft_pages_before)

From 80a361c3c821cce96873b6c0ec8e49e8eed5d30e Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Mon, 14 Aug 2023 14:27:26 -0300
Subject: [PATCH 24/25] test: Check that no logs are emitted in test_rfc_index

---
 ietf/sync/tests.py | 9 ++++++---
 1 file changed, 6 insertions(+), 3 deletions(-)

diff --git a/ietf/sync/tests.py b/ietf/sync/tests.py
index d5e380672..a3e857768 100644
--- a/ietf/sync/tests.py
+++ b/ietf/sync/tests.py
@@ -6,6 +6,7 @@ import os
 import io
 import json
 import datetime
+import mock
 import quopri
 
 from django.conf import settings
@@ -347,9 +348,11 @@ class RFCSyncTests(TestCase):
         draft_abstract_before = draft_doc.abstract
         draft_pages_before = draft_doc.pages
         changes = []
-        for _, d, rfc_published in rfceditor.update_docs_from_rfc_index(data, errata, today - datetime.timedelta(days=30)):
-            changes.append({"doc_pk": d.pk, "rfc_published": rfc_published})  # we ignore the actual change list
-
+        with mock.patch("ietf.sync.rfceditor.log") as mock_log:
+            for _, d, rfc_published in rfceditor.update_docs_from_rfc_index(data, errata, today - datetime.timedelta(days=30)):
+                changes.append({"doc_pk": d.pk, "rfc_published": rfc_published})  # we ignore the actual change list
+        self.assertFalse(mock_log.called, "No log messages expected")
+        
         draft_doc = Document.objects.get(name=draft_doc.name)
         draft_events = draft_doc.docevent_set.all()
         self.assertEqual(len(draft_events) - event_count_before, 2)

From 4706ac2a37bd4b6d6cbb77d37c2dd55326139114 Mon Sep 17 00:00:00 2001
From: Jennifer Richards <jennifer@staff.ietf.org>
Date: Wed, 16 Aug 2023 11:53:58 -0300
Subject: [PATCH 25/25] fix: Restore old handling of draft-iesg state

Missing state -> idexists, idexists -> idexists, anything
else becomes pub.
---
 ietf/sync/rfceditor.py | 17 +++++++++++++++--
 1 file changed, 15 insertions(+), 2 deletions(-)

diff --git a/ietf/sync/rfceditor.py b/ietf/sync/rfceditor.py
index 2e6861959..ce0cf2da2 100644
--- a/ietf/sync/rfceditor.py
+++ b/ietf/sync/rfceditor.py
@@ -475,11 +475,20 @@ def update_docs_from_rfc_index(
                 draft_changes.append(change)
                 rfc_changes.append(change)
 
-            # Draft should be in the "pub" or "approved" draft-iesg state - complain otherwise
+            # Always set the "draft-iesg" state. This state should be set for all drafts, so
+            # log a warning if it is not set. What should happen here is that ietf stream
+            # RFCs come in as "rfcqueue" and are set to "pub" when they appear in the RFC index.
+            # Other stream documents should normally be "idexists" and be left that way. The
+            # code here *actually* leaves "draft-iesg" state alone if it is "idexists" or "pub",
+            # and changes any other state to "pub". If unset, it changes it to "idexists".
+            # This reflects historical behavior and should probably be updated, but a migration
+            # of existing drafts (and validation of the change) is needed before we change the
+            # handling.
             prev_iesg_state = draft.get_state("draft-iesg")
             if prev_iesg_state is None:
                 log(f'Warning while processing {doc.name}: {draft.name} has no "draft-iesg" state')
-            elif prev_iesg_state.slug != "pub":
+                new_iesg_state = State.objects.get(type_id="draft-iesg", slug="idexists")
+            elif prev_iesg_state.slug not in ("pub", "idexists"):
                 if prev_iesg_state.slug != "rfcqueue":
                     log(
                         'Warning while processing {}: {} is in "draft-iesg" state {} (expected "rfcqueue")'.format(
@@ -487,6 +496,10 @@ def update_docs_from_rfc_index(
                         )
                     )
                 new_iesg_state = State.objects.get(type_id="draft-iesg", slug="pub")
+            else:
+                new_iesg_state = prev_iesg_state
+
+            if new_iesg_state != prev_iesg_state:
                 draft.set_state(new_iesg_state)
                 draft_changes.append(f"changed {new_iesg_state.type.label} to {new_iesg_state}")
                 e = update_action_holders(draft, prev_iesg_state, new_iesg_state)