From dce4ed16ee100a0486e4528705eb7bc8b9b5c19b Mon Sep 17 00:00:00 2001 From: Sean Whalen <44679+seanthegeek@users.noreply.github.com> Date: Fri, 24 Jul 2026 22:12:41 -0400 Subject: [PATCH] Clarify that Nested() on auth-result fields is in-memory shape only MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Copilot flagged that _AggregateReportDoc declares dkim_results and spf_results with Nested(...) while the create_indexes comment insists the stored mapping must stay dynamic `object`. Both are true: the Nested declaration only shapes the DSL's in-memory document building and is never installed as a mapping, because create_indexes skips Index.document() registration. Say so at both sites, in both backends, so nobody "fixes" the mismatch by registering the mapping — which would install real nested mappings and blank the dashboards. Co-Authored-By: Claude Fable 5 --- parsedmarc/elastic.py | 12 ++++++++++-- parsedmarc/opensearch.py | 12 ++++++++++-- 2 files changed, 20 insertions(+), 4 deletions(-) diff --git a/parsedmarc/elastic.py b/parsedmarc/elastic.py index f043d8a6..d098ad19 100644 --- a/parsedmarc/elastic.py +++ b/parsedmarc/elastic.py @@ -227,6 +227,11 @@ class _AggregateReportDoc(Document): header_from = Text() envelope_from = Text() envelope_to = Text() + # Nested(...) on the two auth-result fields below is only the DSL's + # in-memory document shape; it is never installed as a mapping. + # create_indexes() deliberately skips Index.document() registration so + # these fields stay dynamic-mapped as plain `object` in the cluster + # (see the comment there and issue #169). dkim_results = Nested(_DKIMResult) spf_results = Nested(_SPFResult) # One "{selector} / {domain} / {result}" (DKIM) or "{scope} / {domain} / @@ -558,8 +563,11 @@ def create_indexes(names: list[str], settings: dict[str, Any] | None = None): # tables, and Grafana's nested bucket aggregation (9.4+) lacks # reverse_nested for parent-level metrics like message_count — # so the dynamic `object` mapping produced by a bare create is - # load-bearing for the shipped dashboards. See issue #169 and - # the *_combined fields on _AggregateReportDoc. + # load-bearing for the shipped dashboards. _AggregateReportDoc + # still declares dkim_results/spf_results with Nested(...), but + # that is only the DSL's in-memory shape for building documents + # — it is never installed as a mapping. See issue #169 and the + # *_combined fields on _AggregateReportDoc. if not index.exists(): logger.debug("Creating Elasticsearch index: {0}".format(name)) if effective_settings: diff --git a/parsedmarc/opensearch.py b/parsedmarc/opensearch.py index e1f8fdfa..de0bfd16 100644 --- a/parsedmarc/opensearch.py +++ b/parsedmarc/opensearch.py @@ -185,6 +185,11 @@ class _AggregateReportDoc(Document): header_from = Text() envelope_from = Text() envelope_to = Text() + # Nested(...) on the two auth-result fields below is only the DSL's + # in-memory document shape; it is never installed as a mapping. + # create_indexes() deliberately skips Index.document() registration so + # these fields stay dynamic-mapped as plain `object` in the cluster + # (see the comment there and issue #169). dkim_results = Nested(_DKIMResult) spf_results = Nested(_SPFResult) # One "{selector} / {domain} / {result}" (DKIM) or "{scope} / {domain} / @@ -476,8 +481,11 @@ def create_indexes(names: list[str], settings: dict[str, Any] | None = None): # tables, and Grafana's nested bucket aggregation (9.4+) lacks # reverse_nested for parent-level metrics like message_count — # so the dynamic `object` mapping produced by a bare create is - # load-bearing for the shipped dashboards. See issue #169 and - # the *_combined fields on _AggregateReportDoc. + # load-bearing for the shipped dashboards. _AggregateReportDoc + # still declares dkim_results/spf_results with Nested(...), but + # that is only the DSL's in-memory shape for building documents + # — it is never installed as a mapping. See issue #169 and the + # *_combined fields on _AggregateReportDoc. if not index.exists(): logger.debug("Creating OpenSearch index: {0}".format(name)) if settings is None: