Der Upstream-Merge holte 70.265 fremde Commits nach ThreadNet-Web; 39 davon
faerbten die Gruppenpruefung als Echtzeit-Stempel rot. Sie verletzen die
Konvention wirklich, konnten ihr aber nie folgen und werden sich nie aendern -
also ein Dauerrot, und ein Dauerrot meldet nichts mehr (#0104).
Neues Feld fremdhistorie in docs/components/*.md. Wo gesetzt, prueft die
Hygiene nur Commits, die von eigenen Identitaeten COMMITTET wurden. Der
Trennschnitt ist gemessen, nicht geraten: 18 eigene Commits, alle von uns
committet; 39 fremde von GitHub/RiotRobot; keine Ueberschneidung. Der Autor
taugt nicht - eigene Commits koennen fremde Autoren tragen (Cherry-Picks).
Der Feldwert ist die Begruendung, kein Schalter - Muster der Quittungen aus
ADR-0020. Die Ausnahme gilt nur, wo sie deklariert ist, nicht global; der Preis
(ein Commit unter voellig unbekannter Identitaet faellt dort durchs Raster)
steht in den Konsequenzen.
Belegt: nach dem Fix 0 offene Befunde bei 20 Quittungen, und in ThreadNet-Web
werden weiterhin 18 Commits geprueft, alle auf 12:00:00.
Ausserdem zurueckgenommen: mein Nachtrag an ADR-0022. Eine angenommene ADR wird
nicht editiert (Regel in der Vorlage) - die Erkenntnis steht jetzt in #0099.
ThreadNet-Web:main auf 8ca03fe, Merge-Commit 88c4e15 mit beiden echten Eltern.
GHSA-wrcp-5v3v-3j6v ist mit dem Versionssprung erledigt.
Drei Anlaeufe: rc.1 erzeugte kein Image (.npmrc fehlte im Docker-Kontext, unter
pnpm 11 fatal), rc.2 ging live und brach die Raumliste, rc.3 bestand die Abnahme.
ADR-0022 um zwei Korrekturen ergaenzt, die erst die Ausfuehrung gezeigt hat:
Die stille Klasse verschwindet nicht ganz, sie dreht sich um. Der Merge meldet,
wenn Upstream eine Datei verschiebt - das hat gehalten. Er meldet nicht, wenn
beide Seiten die Aufloesung ueberleben und nur eine noch Sinn ergibt. Genau das
brach rc.2.
Und: ein gruener Build war nie eine Abnahme. Der web-Job baut nur, webpack
wirft Typen weg. tsc meldete den Fehler durchgehend, gefragt hatte ihn niemand.
Seit 8ca03fe fuehrt docker_web den Job typecheck als needs.
Daraus die stehende Regel fuer kuenftige Merges: nach der Konfliktaufloesung auf
ueberlebende Reste pruefen, nicht nur auf verlorene Zeilen. Wo Zeichenketten
statt Typen im Spiel sind, braucht es einen Abgleich gegen die Registry - fuer
Einstellungen einmalig gefahren, 135 abgefragte gegen 152 registrierte.
Decision sorb: option B, a one-time real merge rather than a shared replace ref. What
decided it was visibility, not effort - a replace ref works only while everyone
remembers to fetch it, and for a repo whose core problem is "git says nothing", a
mechanism that silently differs per clone is the wrong shape.
The test corrected the option's own description. --allow-unrelated-histories on its own
gives a two-way comparison and 1757 conflicts; with the graft set locally it is 32. So
the graft is not the alternative to B, it is how B is performed: set it, let the merge
compute against it, commit, and the merge commit then carries the real parents so the
graft can go.
Also recorded because it cost time and looked like a fundamental problem: tags fetched
with --depth=1 leave a shallow boundary, so v1.12.26 was walled off at a single commit
even though develop carried the same commit in full. Every merge attempt failed with
"refusing to merge unrelated histories" until fetch --unshallow.
The merge itself is measured but deliberately not executed. apps/web/package.json
carries two product decisions rather than conflicts - our Element Call fork against
upstream's, and a matrix-js-sdk git pin against a released version - and resolving the
first one wrongly would silently delete the noise suppression work from #0054. Neither
is safe without a build and the ClamAV functional test.
With the full upstream history the common ancestor could be searched rather than
guessed: measuring tree distance, not dates, over every develop commit in the import
window. deadd548 from 2026-05-08 wins with 43 differing files against 783 for the tag
we wrongly assumed. Thirty-one of those are mode-only, the historical import bug, and
the content remainder is precisely our own Discord feature plus LICENSE and CHANGELOG.
Tested in a throwaway clone rather than asserted: without the graft there is no
merge-base, with it deadd548 becomes one, and merging three months of develop yields
32 conflicting files. Only four are source, and they are exactly our patches - the
rest are GitHub workflows we do not use and the lockfile.
One intermediate result was worthless and nearly passed as success: merging v1.12.26
reported zero conflicts while not running at all, because the tag was fetched shallow
and carries a single commit. Checking MERGE_HEAD is what caught it.
The real gain is not the number. The issue warns that if Element moves a viewmodels
file during the MVVM rework, our lines vanish without a conflict and git says nothing.
With a genuine ancestor, git says something.
Adopting this is ADR-worthy either way - a shared replace ref or a one-time unrelated-
histories merge - so the decision stays with sorb.
upstream is configured and both tags are fetched, so updates can finally be looked at
as a diff. The first diff disproved what this issue and the fork doc have both said
since August: the import is not the v1.12.17 tag. 783 files differ, 115 exist only on
our side, and two of them date the tree - TextualBodyViewModel.tsx and
RovingTabIndex.tsx are absent from the tag, present in v1.12.26 and present in our
import. The changelog stops at 1.12.17 only because it is written at release time.
So the base is a develop state after the 1.12.17 release. Fittingly it is the MVVM
files that prove it - the area this issue already warned about.
Step 3 changes accordingly: grafting onto v1.12.17 would put every future merge on an
invented ancestry, and worse than raising false conflicts, it could hide real ones.
Finding the actual ancestor means searching develop history, which needs the full
upstream repository rather than a shallow tag fetch.
The advisory assessment is unchanged - the base sits below 1.12.22 either way.
Decision sorb, and it needed two files rather than one: the desktop client loads its
own config.json, so changing only element-values.yaml would have hardened the web
client and left the distributed builds alone - the themes-rollout trap again.
Read out of our own code rather than assumed: the edit button beside the server name
disappears, and the 401/403 login error names the server. That is a surface
restriction. MatrixChat still accepts hs_url from the query string in two registration
flows without consulting the setting, so a crafted link is untouched.
Recorded as a narrowing, not a mitigation. GHSA-wrcp-5v3v-3j6v still applies to
1.12.17 and the upstream update in steps 2-4 remains the actual fix.
The issue insists on measuring before building, so: the import was Element Web
1.12.17 in May, upstream is at v1.12.26, and GHSA-wrcp-5v3v-3j6v has been open since
2026-07-20 with "affected: < v1.12.22". We are inside that range. The other five
advisories concern 1.11.x and do not.
Severity is medium and the attack needs the client pointed at a malicious homeserver,
which our users have no reason to do. But the precondition is reachable:
disable_custom_urls is not set, so Element accepts an arbitrary homeserver by default,
and the desktop builds are handed out.
So the honest answer is overdue rather than urgent: a fork without a merge path took
four weeks to not close a known hole, which is exactly the latency this issue names as
the real problem.
One mitigation is available today and independent of the fork question - setting
disable_custom_urls removes the precondition for our users in one line. With
federation closed it is a reasonable setting on its own merits.
ADR-0012 made docs/issues/ canonical for the management scope only and left
gitops, ThreadNet-Web and threadnet-call on GitLab "until the component adopts".
That split produced exactly what it invited: two numbering worlds where
management#20 and gitops#20 are different issues, drift nobody had to answer for
(gitops#61 carried no milestone since 2026-08-11), and component backlogs that
host sessions without lab access cannot read at all.
The 46 open component issues are now files 0056-0101. The file id is the
group-wide identifier; provenance lives in the frontmatter (new field `projekt`
plus gitlab_iid) and in the filename, so "gitops#61" still finds 0091. Bodies are
copied verbatim; comments and history stay on GitLab, as with the 2026-08-11
management import.
Both scripts learned the second dimension: spiegel_issues.py routes each file to
its origin project, reopens issues that are open in the repo but closed on the
board, and writes the new iid back after creating one; gruppenpruefung.py checks
drift across all four trackers instead of management alone. What the mirror
cannot decide stays a finding, not a silent state.
Two things needed a hand, both recorded in the files: gitops#61 had no milestone
(M1 - it is a live account-takeover path) and carried two area labels where the
schema holds one. The Gitea migration footers in the imported bodies point at
decommissioned trackers; their links are removed, the provenance sentence stays.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>