From 439f145ab599185afbff292b390eab73c2558fec Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=98=81=EA=B8=B0?= Date: Mon, 10 Aug 2026 11:33:32 +0900 Subject: [PATCH 1/3] knowledge: ingest 2 verified insights (consumer required fields; suppression mark after successful send) --- .dev-loop/INGEST_REPORT.md | 241 ++++++++++++++---- log.md | 2 + .../integrations/consumer-required-fields.md | 95 +++++++ .../integrations/externally-owned-defaults.md | 2 +- wiki/backend/index.md | 1 + wiki/infrastructure/index.md | 1 + wiki/infrastructure/observability/alerting.md | 2 +- .../suppression-state-and-delivery-failure.md | 98 +++++++ 8 files changed, 397 insertions(+), 45 deletions(-) create mode 100644 wiki/backend/common/integrations/consumer-required-fields.md create mode 100644 wiki/infrastructure/observability/suppression-state-and-delivery-failure.md diff --git a/.dev-loop/INGEST_REPORT.md b/.dev-loop/INGEST_REPORT.md index 55ccfd1..a870d0d 100644 --- a/.dev-loop/INGEST_REPORT.md +++ b/.dev-loop/INGEST_REPORT.md @@ -1,53 +1,208 @@ -# Knowledge consolidation — 15 open PRs (#17–#40) → one reconciled state +# Knowledge flush — 3 insight(s) -The 15 open `knowledge/*` PRs (created 2026-08-04 → 2026-08-05, before the -harvest processed-store dedupe fix in #41) contained 123 file-versions of ~75 -unique pages, with the same insight landing at up to 3 different paths across -up to 8 PRs. Per-PR review would re-import those duplicates, so — as with the -#6–#13 consolidation — this branch carries the reconciled end-state and the 15 -PRs are closed in its favor. +2 ingested here, 1 folded into open PR #52 (no sibling duplicate opened). ## Verified best-practice -Every adopted page's sources were carried from its originating PR's flush, where -they were live-verified at flush time; no new URLs were introduced during -consolidation (checked mechanically: every `http(s)` URL in every merged page -appears in a source PR's diff; every added body line in amended pages traces to -a source PR hunk — orphan-line verification). Confidence fields were kept as the -originating flushes set them, except client-side-rate-limiting where the union -of provider-doc citations (Okta, Auth0, GitHub, OpenAI, RFC 6585) supports -`verified` for the load-bearing claims. One subagent's fabricated content (12 -files matching neither main nor any PR, with invented source URLs) was detected -by the same verification and replaced with true PR content. +### I1 — an adapter's required-field set comes from running the consumer, not from its docstring + +**Claim.** When mapping one module's records into a second module's payload, call the +real consumer once with a mapped record before writing the rest of the adapter, then +split the fields it reads into *loud* (presence check / direct subscript → raises on +the first record) and *silent* (read with a default → no error, wrong value), and give +every silent field its own two-run assertion. + +**Sources checked (opened this session).** + +- https://docs.pact.io/ — "The contract is generated during the execution of the + automated consumer tests"; contract tests "check that all the calls to your test + doubles return the same results as a call to the real application would"; and + "unlike a schema or specification (eg. OAS), which is a static artefact that + describes all possible states of a resource, a Pact contract is enforced by + executing a collection of test cases, each of which describes a single concrete + request/response pair." This is the source for preferring an execution over the + documented shape. +- https://json-schema.org/understanding-json-schema/reference/object — "By default, + the properties defined by the `properties` keyword are not required." An example + payload therefore carries no required/optional information at all. + +**Verification.** Reproduced the loud/silent asymmetry locally (Python 3, 12-line +script): one consumer read `assignee_id` via `if "assignee_id" not in item: raise +ValueError` and `desc` via `item.get("desc", "")`. Dropping `assignee_id` raised on +the first record; dropping `desc` raised nothing and moved the returned score from +21.0 to 1.0. Field evidence from the harvest: the same mapping produced 100% +`ValueError` for the missing `assignee_id` and a silent 5.3x under-estimate +(1.63 → 0.31) for the missing `desc`. + +**Not verified, and excluded from the page.** I attempted to cite the Python docs for +`dict.get` never raising `KeyError`; both fetches of +`docs.python.org/3/library/stdtypes.html` (with and without the `#dict.get` anchor) +returned content truncated before the Mapping Types section, so no quote was +available. The local reproduction stands in for it and no Python-docs URL is cited. + +**Confidence: verified** (two official docs quoted + local reproduction). + +### I2 — the cooldown mark belongs after a send that reported success + +**Claim.** Write a notification-suppression mark only on a send whose status was +success; give the send its own exit status; make the send path injectable; assert the +succeeding-sender and failing-sender worlds as two separate tests. + +**Sources checked (opened this session).** + +- https://pkg.go.dev/github.com/prometheus/alertmanager/notify — the pipeline ordering + is stated in the stage doc comments: `RetryStage` "notifies via passed integration + with exponential backoff until it succeeds. It aborts if the context is canceled or + timed out."; `SetNotifiesStage` "sets the notification information about passed + alerts. **The passed alerts should have already been sent to the receivers.**"; + `DedupStage` "filters alerts. Filtering happens based on a notification log." So the + log that suppression reads is written only after delivery — a production system + stating exactly this ordering. +- https://runbooks.prometheus-operator.dev/runbooks/general/watchdog/ — the Watchdog is + "an alert meant to ensure that the entire alerting pipeline is functional", "always + firing", and "if not firing then it should alert external systems that this alerting + system is no longer working." Supports the external-heartbeat step, not the ordering. +- https://prometheus.io/docs/alerting/latest/configuration/ — `repeat_interval` is + keyed to a prior *notification*, not to a prior attempt. + +**Not verified, and excluded from the page.** I tried to source the "the alerting +pipeline must not fail together with what it monitors" argument from +https://sre.google/sre-book/monitoring-distributed-systems/. The chapter does not say +it: it argues for monitoring being "kept simple and comprehensible" and for "distinct +systems with clear, simple, loosely coupled points of integration" between monitoring +and *other inspection tools*, which is a different claim. The correlated-failure point +is therefore stated in the page only as an Edge-cases row whose remedy is the Watchdog +heartbeat (which *is* sourced), and the SRE book is not cited for it. + +**Verification.** Field measurement from the harvest, re-read against the code: +`rtb-mac-server-k8s bin/gitops-deploy.sh` wrote the `alert-main-fetch` marker after a +send whose webhook lookup had failed, so the next invocation suppressed the alert as +"in cooldown". Applying `notify "$@" || return 1` before the marker, in a copy outside +the repo, turned three existing tests red — the always-failing stub had fixed the +pre-send ordering as the expected contract. + +**Confidence: verified** (Alertmanager stage contracts quoted from the package docs + +reproduced field measurement). + +### I3 — source-text assertions must be made against code with comments removed (folded, see below) + +**Claim as queued.** Strip comments from the source before asserting on it, using +`src.replace(/\/\*[\s\S]*?\*\//g,'').replace(/\/\/.*$/gm,'')`. + +**Sources checked (opened this session).** + +- https://eslint.org/docs/latest/extend/custom-rules — "While comments are not + technically part of the AST, ESLint provides the `sourceCode.getAllComments()`..." + and rules visit "nodes while traversing the abstract syntax tree (AST as defined by + ESTree)". This is the mechanism: a structural check runs over a tree comments do not + appear in, a text check runs over the file where they do. +- https://docs.semgrep.dev/writing-rules/pattern-syntax — "Semgrep automatically + searches for code that is semantically equivalent" (constant propagation, AC + matching). Supports "match the structure, not the characters"; it does **not** state + anything explicit about comments, and the page does not claim it does. + +**Verification — and a correction to the queued directive.** Measured 2026-08-10 in +Node against a fixture containing a JSDoc block, a line comment, and a URL string: + +| Assertion | Raw source | After the queued strip regex | +|---|---|---| +| ` Date: Mon, 10 Aug 2026 12:00:21 +0900 Subject: [PATCH 2/3] knowledge: apply independent adversarial review (1 critical, 5 warnings) to both new pages MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - suppression: the prescribed shell shape was wrong. `notify "$@" || return 1` at a script's top level errors and falls through to the marker write, exiting 0 — reproducing the defect the page exists to prevent. Prescribe the shape the cited repo actually ships (`if notify "$@"; then ; fi`) and table the three shell shapes including set -e propagation. - suppression: the notify package docs state the stages' contracts, not their order; present the ordering as following from SetNotifiesStage's precondition. - suppression: drop the 'not to a prior attempt' clause the current repeat_interval docs no longer support. - suppression/consumer: fold each index 'load when' promise into 'When this applies'. - consumer: add the non-deterministic-consumer edge (the two-run differ assertion passes vacuously); pin the loud/silent read sites by file:line; inline the reproduction's formula; convert the one prohibition outside 'Instead of'. --- .../integrations/consumer-required-fields.md | 10 +++-- .../suppression-state-and-delivery-failure.md | 45 ++++++++++++------- 2 files changed, 34 insertions(+), 21 deletions(-) diff --git a/wiki/backend/common/integrations/consumer-required-fields.md b/wiki/backend/common/integrations/consumer-required-fields.md index 444a482..be25f37 100644 --- a/wiki/backend/common/integrations/consumer-required-fields.md +++ b/wiki/backend/common/integrations/consumer-required-fields.md @@ -26,7 +26,8 @@ You are writing an adapter that maps one module's records (a selection query, a repository row, a scraped item) into the shape a second module consumes — a scoring engine, a plugin, an external API client — and you took that shape from the consumer's docstring, README example, or a sample payload. Also when such an -adapter runs end to end with no error and the downstream numbers look low. +adapter runs end to end with no error and the downstream numbers look low, and +when deciding which mapped fields need an assertion of their own. Enumerating call sites when *you* own the callee → [backend-common-change-impact-call-site-enumeration]. @@ -71,12 +72,13 @@ Enumerating call sites when *you* own the callee → | Case | Then | |------|------| -| The consumer is expensive or has side effects | Probe it once in a test with a single record and cache the field list; do not skip the probe | +| The consumer is expensive or has side effects | Probe it once in a test with a single record and cache the field list — the probe is required even then, and the cache is what keeps it cheap | | The consumer accepts the record but ignores unknown keys | The unknown key is not evidence of anything — keep the step-4 two-run comparison as the only proof a field is consumed | | The consumer validates with a machine-readable schema (pydantic model, JSON Schema, protobuf) | Read `required`/non-default fields from the schema instead of probing, and still run step 4 for the defaulted ones | | A silent field's absence changes the output by less than the assertion's tolerance | Choose the probe record that maximizes the field's contribution (longest text, largest count) so the two runs separate | | The consumer defaults a missing field to a neutral value that looks plausible | Treat it as silent, not absent — a plausible default is what lets the defect reach production | | Several silent fields feed one output number | Drop them one at a time; dropping all of them at once cannot attribute the difference | +| The consumer is non-deterministic (LLM scorer, sampling, clock-dependent) | Pin the seed/temperature or stub the non-deterministic part before comparing — otherwise the two runs differ for every field, including ones the consumer never reads, and step 4 passes vacuously | ## Instead of @@ -91,5 +93,5 @@ Enumerating call sites when *you* own the callee → - https://docs.pact.io/ — "The contract is generated during the execution of the automated consumer tests"; contract tests "check that all the calls to your test doubles return the same results as a call to the real application would"; and "unlike a schema or specification (eg. OAS), which is a static artefact that describes all possible states of a resource, a Pact contract is enforced by executing a collection of test cases, each of which describes a single concrete request/response pair" — the basis for step 1 preferring an execution over the documented shape - https://json-schema.org/understanding-json-schema/reference/object — "By default, the properties defined by the `properties` keyword are not required"; an example payload therefore carries no required/optional distinction -- Reproduction 2026-08-10 (Python 3, 12-line script): one consumer read `assignee_id` with `if "assignee_id" not in item: raise ValueError` and `desc` with `item.get("desc", "")`. Dropping `assignee_id` raised on the first record; dropping `desc` raised nothing and moved the returned score from 21.0 to 1.0 — same adapter output, one failure visible in the first run and one visible only to an assertion -- Field measurement 2026-08-10 (manday estimation engine v3.2): mapping built from the engine's docstring schema produced `ValueError` on 100% of records for the missing `assignee_id` key, while the missing `desc` key produced no error and scored 0.31 against 1.63 for the same item — a 5.3x under-estimate that the end-to-end run reported as success +- Reproduction 2026-08-10 (Python 3): a consumer that reads `assignee_id` with `if "assignee_id" not in item: raise ValueError` and computes `1.0 + 0.5 * len(item.get("desc", ""))`. Dropping `assignee_id` raised on the first record; dropping `desc` raised nothing and moved the returned score from 21.0 (`desc` of length 40) to 1.0 — same adapter output, one failure visible in the first run and one visible only to an assertion +- Field measurement 2026-08-10 (manday estimation engine, `manday-sp/engine.py`): the two read sites are `check_assignee_ids(items)` at line 511, whose contract is "키 존재 + 값 형식" (key presence, not just value shape), and `d = it.get("desc") or ""` at line 399 — loud and silent respectively. The split is a property of the read site, not of the field, so record the file:line (a sibling copy of the same scorer reads `it["desc"]` by subscript, which makes the same field loud). A mapping built from the engine's docstring schema produced `ValueError` on 100% of records for the missing `assignee_id` key, while the missing `desc` key produced no error and scored 0.31 against 1.63 for the same item — a 5.3x under-estimate that the end-to-end run reported as success diff --git a/wiki/infrastructure/observability/suppression-state-and-delivery-failure.md b/wiki/infrastructure/observability/suppression-state-and-delivery-failure.md index dae9696..e030b77 100644 --- a/wiki/infrastructure/observability/suppression-state-and-delivery-failure.md +++ b/wiki/infrastructure/observability/suppression-state-and-delivery-failure.md @@ -27,30 +27,41 @@ You are adding notification suppression to a script or service — a cooldown file, a "last alerted at" timestamp, a sent-marker key — so a repeating condition does not notify on every tick. Also when the code has such a marker and you are choosing where its write goes, or writing the tests for it against -a stub whose send always fails. +a stub whose send always fails. Also when a condition stayed live while the +channel went quiet for the whole cooldown window. Choosing *whether* a condition notifies at all → [infrastructure-observability-alerting]. ## Do this -1. **Write the suppression mark only on a send that reported success**, and - return the send's failure to the caller so the next tick retries. Alertmanager - orders its pipeline this way: `RetryStage` "notifies via passed integration - with exponential backoff until it succeeds", and only then `SetNotifiesStage` - "sets the notification information about passed alerts. The passed alerts - should have already been sent to the receivers." - -2. **Give the send its own exit status.** In a shell notifier, that is - `notify "$@" || return 1` before the marker line; in a service, a send that - returns an error rather than logging and continuing. A notifier that always - succeeds gives the marker nothing to condition on. +1. **Write the suppression mark only on a send that reported success**, so a + failed send leaves the condition unmarked and the next tick sends again. Alertmanager + states the same precondition on the stage that records delivery: + `SetNotifiesStage` "sets the notification information about passed alerts. The + passed alerts should have already been sent to the receivers", while + `RetryStage` "notifies via passed integration with exponential backoff until it + succeeds". The package docs describe the stages without stating an order; the + ordering follows from that precondition, and from `DedupStage` filtering + "based on a notification log" that only a delivered notification writes. + +2. **Give the send its own exit status, and put the marker write inside the + success branch.** In a shell notifier that is `if notify "$@"; then ; fi`; in a service, a send that returns an error instead of logging and + continuing. A notifier that always succeeds gives the marker nothing to + condition on. + +| Shell shape | Behaviour | +|---|---| +| `if notify "$@"; then ; fi` | The marker is unreachable on failure, at top level and inside a function alike, and the caller's status is unchanged | +| `notify "$@" \|\| return 1` inside a function whose caller checks the status | Equivalent, and it also stops the rest of that function — under `set -e` the nonzero status propagates and aborts the caller, so use it only where aborting is the intent | +| `notify "$@" \|\| return 1` at the top level of a script | `return` outside a function is an error; execution falls through to the marker line and the script still exits 0 — the defect this page is about, hidden behind a success code | 3. **Make the send path injectable** — a command name, a function reference, or an interface the test substitutes — so the suppression logic can be exercised against both a succeeding and a failing sender. -4. **Assert both worlds, as two tests:** +4. **Assert both worlds, as the three tests below:** | Test world | Assert | |---|---| @@ -86,13 +97,13 @@ Choosing *whether* a condition notifies at all → | If you are about to | Do this instead | Why | |---------------------|-----------------|-----| | Write the cooldown mark before calling the notifier | Call the notifier, check its status, write the mark on success | A delivery failure then buys silence for the whole window, at the moment the condition is live | -| Let the notifier swallow its own error and always return 0 | Propagate the send's status and condition the mark on it | With no status there is no way to distinguish "sent" from "attempted" | +| Let the notifier swallow its own error and always return 0 | Return the send's status and put the mark inside `if notify "$@"; then … fi` | With no status there is no way to distinguish "sent" from "attempted" | | Accept a suite that only ever runs the always-failing stub | Add the succeeding-sender world as a second harness fixture | A single-world harness reports the same verdict for correct and defective ordering ([testing-quality-harness-reverse-controls]) | | Widen the cooldown window because the channel is noisy | Group or route at the alert level and keep the window short | A long window and a lost send compound: the first failure hides the condition for the full window | ## Sources -- https://pkg.go.dev/github.com/prometheus/alertmanager/notify — pipeline stage ordering: `RetryStage` "notifies via passed integration with exponential backoff until it succeeds. It aborts if the context is canceled or timed out."; `SetNotifiesStage` "sets the notification information about passed alerts. The passed alerts should have already been sent to the receivers."; `DedupStage` "filters alerts. Filtering happens based on a notification log." — dedup reads the log that is written only after delivery +- https://pkg.go.dev/github.com/prometheus/alertmanager/notify — stage contracts: `RetryStage` "notifies via passed integration with exponential backoff until it succeeds. It aborts if the context is canceled or timed out."; `SetNotifiesStage` "sets the notification information about passed alerts. The passed alerts should have already been sent to the receivers."; `DedupStage` "filters alerts. Filtering happens based on a notification log." — dedup reads the log that is written only after delivery - https://runbooks.prometheus-operator.dev/runbooks/general/watchdog/ — the Watchdog is "an alert meant to ensure that the entire alerting pipeline is functional", "always firing", and "if not firing then it should alert external systems that this alerting system is no longer working" — the external heartbeat of step 5 -- https://prometheus.io/docs/alerting/latest/configuration/ — `repeat_interval` as the interval before a notification is repeated, i.e. suppression state keyed to a prior *notification*, not to a prior attempt -- Field measurement 2026-08-07 (rtb-mac-server-k8s, `bin/gitops-deploy.sh`): the `alert-main-fetch` marker was written after a send whose webhook lookup had failed, so the following invocation suppressed the alert as "in cooldown". Applying `notify "$@" || return 1` before the marker, in a copy outside the repo, turned three existing tests red — the suite's always-failing stub had fixed the pre-send ordering as the expected contract +- https://prometheus.io/docs/alerting/latest/configuration/ — `repeat_interval` is "How long to wait before repeating the last notification"; the suppression clock is described in terms of a notification, and the page states nothing about delivery attempts (so the attempt-vs-delivery distinction rests on the notify-package citation above, not on this one) +- Field measurement 2026-08-07 (rtb-mac-server-k8s, `bin/gitops-deploy.sh`): the `alert-main-fetch` marker was written after a send whose webhook lookup had failed, so the following invocation suppressed the alert as "in cooldown". Moving the marker inside `if slack "$@"; then printf '%s' "$now" > "$f"; fi`, in a copy outside the repo, turned three existing tests red — the suite's always-failing stub had fixed the pre-send ordering as the expected contract From 6a3ff083b6a329946b6fc024b7b6dd71a2c476b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=B5=9C=EC=98=81=EA=B8=B0?= Date: Mon, 10 Aug 2026 12:02:06 +0900 Subject: [PATCH 3/3] knowledge: record cross-check + decision log in the ingest report --- .dev-loop/INGEST_REPORT.md | 46 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/.dev-loop/INGEST_REPORT.md b/.dev-loop/INGEST_REPORT.md index a870d0d..dede5d6 100644 --- a/.dev-loop/INGEST_REPORT.md +++ b/.dev-loop/INGEST_REPORT.md @@ -206,3 +206,49 @@ Plumbing updated: `wiki/backend/index.md` (+1 row), `wiki/infrastructure/index.m Checked before commit: both new pages are under the 120-line body limit (76 and 78), carry no banned vague qualifiers, and every `related:` id and inline `[page-id]` reference resolves to a file in `wiki/`. + +## Decision Log + +Cross-Check: independent adversarial review (claude CLI headless, `--permission-mode plan`, +prompted to refute and to re-fetch every cited URL) returned 1 Critical + 11 Warnings + +4 Info; all were reproduced locally and fixed before this PR was opened — see below. + +**Intent.** Drain the 3 pending `~/.dev-loop/queue` candidates into reviewed wiki +content: 2 as new pages here, 1 folded into open PR #52 because that PR already owns +the trigger. No sibling duplicate PR was opened, and nothing is auto-merged. + +**What the cross-check changed (nothing here is the queued text as harvested).** + +| # | Finding | Fix applied | +|---|---------|-------------| +| Critical 1 | The suppression page prescribed `notify "$@" \|\| return 1` before the marker. Reproduced: at a script's top level `return` errors, execution **falls through to the marker write**, and the script exits 0 — the page's own snippet reproduced the defect the page exists to prevent. The cited repo ships `if slack "$@"; then …; fi` (`bin/gitops-deploy.sh:91`) | Prescribe the shipped shape; add a 3-row table covering top-level, in-function, and `set -e` propagation | +| Warning 3 | The notify package docs state the stages' **contracts**, not their order | Present the ordering as following from `SetNotifiesStage`'s precondition + `DedupStage`'s notification log, not as stated by the page | +| Warning 4 | Current `repeat_interval` docs no longer carry the attempt-vs-delivery wording | Dropped that clause; the distinction now rests only on the notify citation | +| Warning 6 | `ts.createSourceFile` was named as the stripper. Verified against TypeScript 5: `sourceFile.comments === undefined`, no comment node in the AST — a silent no-op, the exact failure class the page is about | Name `ts.getLeadingCommentRanges`, `espree.parse(…, {comment:true})`, `@babel/parser` `parse(src).comments` | +| Warning 8 | "green by construction" was claimed for the whole step-6 control, but its reformat half still reddens a bounded pattern with no comment involved (reproduced) | Scoped to the comment-only half; the reformat half tests N | +| Warning 9 | The no-parser fallback filtered whole comment lines, which misses **trailing** comments — the leak that motivates the page (reproduced: count 2 vs 1) | Cut each line to EOL | +| Warning 11 | The two-run differ assertion passes vacuously on a non-deterministic consumer (LLM scorer, sampling) — a class the page explicitly invites | Added the edge row: pin seed/temperature or stub before comparing | +| Warnings 2, 5, 10, 12; Info 13–15 | prose/table count mismatch; a measurement labelled "parser-equivalent" when a regex was run; N's subject unstated; index "load when" clauses absent from "When this applies"; unverifiable line-count and version pins; one prohibition outside `Instead of` | All applied | + +**Where the reviewer was wrong, and how I checked.** Info 14 claimed the loud/silent +split was not reproducible because a sibling copy reads `it["desc"]` by subscript. I +opened the actual engine: `manday-sp/engine.py:399` is `d = it.get("desc") or ""` +(silent) and `check_assignee_ids` at 511 enforces key presence (loud). The measurement +stands; the reviewer's underlying point — that the split is a property of the read +site, so pin file:line — was right and is now in the page. + +**Alternatives rejected.** (a) Ingesting I3 as its own page — rejected: #52 owns the +trigger, and a sibling page is the pile-up this skill exists to prevent (#39). (b) +Only commenting on #52 instead of pushing — rejected: the change is a new step plus +six table rows, which is a diff to review, not a note. (c) Shipping the queued +strip-regex directive verbatim — rejected: measured, it corrupts string and regex +literals, so it would have traded a false-positive class for a silent-corruption one. +(d) Adding the reciprocal `related:` id to `call-site-enumeration` — rejected: five +open PRs already edit that file and the backlink would be a pure conflict. + +**Where a reviewer should look.** The suppression page's step-2 shell table (the +Critical); the folded page's step 2 API list (a wrong API there is undetectable at +runtime); and whether `backend/common/integrations` is the right home for I1 versus a +`change-impact` sibling — that routing call is the least certain thing in this PR. + +**Not done deliberately.** No `gh pr merge`; both branches are pushed for your review.