64 Commits
Author SHA1 Message Date
Thore Cimbal 6731d2a70f ADR-0023: fremde Historie erklaert von der Git-Hygiene ausnehmen
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.
2026-08-19 12:00:00 +00:00
Thore Cimbal 6652125ed3 #0099 geschlossen: Upstream-Anschluss vollzogen, v0.6.0 in Produktion
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.
2026-08-19 12:00:00 +00:00
Thore Cimbal bae97e6ca6 docs(adr): ADR-0022 for the upstream reconnection, corrected by the test
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.
2026-08-19 12:00:00 +00:00
Thore Cimbal 22775e499c docs(issues): close #0031 and #0104 - green is the normal state again
Pipeline 540 has both management checks passing, and canonize_rotation has been green
since yesterday. That is the condition AGENTS.md's alarm rule silently assumed, and it
holds again.

#0031 is done because the Authentik blueprint check now runs rather than skipping:
sorb supplied the variables, the token carries exactly one permission on its own
service account, and the run says "Geprueft: 11 Projekte" with no skip line. The blind
spot it named - a blueprint discarded on every pass while Flux reported green - would
now surface.

#0104 closes on all four criteria. Twice the route was the cause rather than an
acknowledgement: gitops had no workflow block and created pipelines with no jobs,
which is red without a fault, and management had the same gap for API triggers and was
closed pre-emptively. Exactly one thing is acknowledged, because it cannot be unmade -
pipeline 518 exists in history and sits inside the check's eight-day window, so the
entry expires with the window on 2026-08-28.

From 25 open findings this morning to none, with nothing hidden: 26 entries carry a
reason and a date in the log.
2026-08-19 12:00:00 +00:00
Thore Cimbal f4f0f997bb docs(issues): close #0060 - group call verified with federation closed
sorb tested a group call after the restart and it works. That proves what curl could
not: the full OpenID token check through /_matrix/federation/v1/openid/userinfo still
completes with federation closed, so the whitelist genuinely does not reach that
endpoint.

It is also where option C finally died. Blocking /_matrix/federation at the edge would
have removed exactly this path, and nothing before the call would have shown it.
2026-08-19 12:00:00 +00:00
Thore Cimbal 15134d1846 docs(adr): ADR-0021 - federation closed, and why the tighter option was wrong
sorb chose C, fully disabling federation at the edge. Building it showed that would
have killed group calls: lk-jwt-service verifies OpenID tokens through
/_matrix/federation/v1/openid/userinfo and reaches it over the public name, so a path
block on /_matrix/federation is the mrtc outage again with a different cause. C was
dropped and B implemented.

The ADR records the measurement the decision rests on - zero destinations, zero remote
users, zero rooms with outside participation in four months - and the trap, so nobody
completes C later as a quick follow-up. Doing that safely means binding the auth
service to Synapse in-cluster first, which is its own undertaking with a call
acceptance.

Also recorded because it nearly slipped through: Flux applied the ConfigMap while
Synapse kept running its old config from 2026-08-01. The config is rendered at pod
start, so the change was inert until a restart - the same class as #0044. Verified
afterwards inside the running process rather than in the ConfigMap, with the client
API and the OpenID endpoint still answering.
2026-08-19 12:00:00 +00:00
Thore CimbalandClaude Opus 5 f2d1adb407 docs(issues): close #0082 - its headline was fixed the day it was written
"Alert delivery is disconnected" was true when the issue was filed and untrue by
that evening. I carried it as a production blocker on the strength of the issue text
alone, which was wrong: the rules aggregate per target, save_state runs inside the
loop including the failure branch, the security null route is gone, and coturn is
pinned. All of it verified in the code, not inferred.

What genuinely remained were the two smaller review points, and both are done now.
The exporter no longer erases first-seen timestamps when a report fails to parse -
pruning is limited to targets actually read this round, proven in both directions.
And because TrivyScanStale cannot by construction report a target that never
produced a report, the exporter now emits trivy_reports_total and
trivy_report_read_errors with an alert on each.

Priority corrected from high to medium to match what was actually open.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:00:00 +00:00
Thore CimbalandClaude Opus 5 3f5da21f3e docs(issues): close #0084 - the token exists and is proven to work
sorb created the project access token with exactly what the job needs: Maintainer,
because main is protected with push=Maintainers, and write_repository only, because
the job pushes and never calls the API. Checked through the API without touching the
value.

Proven rather than assumed: a throwaway job pushed a ref with that token and removed
it again, leaving main untouched and no branch behind. So the value reaches the job -
protected variable on a protected branch - and it may write. The first real test is
the rotation on 2026-09-01.

Recorded as a future silent failure: the token expires 2027-08-19, and because the
job runs green while idle, nobody would notice until the next real rotation after
that date. Exactly the #0104 class, so it belongs in a calendar rather than in hope.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:00:00 +00:00
Thore CimbalandClaude Opus 5 63337d3429 docs(issues): close #0008 with its reservation stated
Decision sorb. Both questions are answered: there is no rule for 9090 or 3100 on
CFGMON and Hetzner denies inbound by default, so Weg A never had anything to
restrict; and the state now has ports-soll.md plus pruefe-ports.sh holding it.

The reservation is written down rather than glossed over: from sorb's network the
must-be-shut half cannot be proven, because the site coupling grants privileged
access there. The script says so instead of reporting false green, and an external
run - judged excessive for routine use - is named for the case that warrants it.

Whether the GAME push survives the vSwitch move belongs to #0002.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-19 12:00:00 +00:00
Thore CimbalandClaude Opus 5 8e653082b6 docs(adr): ADR-0020 for the acknowledgement mechanism
The framework audit found this missing. Acknowledging known findings is a process
decision about how the alarm system treats exceptions, and AGENTS.md is explicit
that documenting an exception instead of deciding it is itself the error. It lived
only inside #0104 and #0105, which are issues, not decision records.

The ADR carries the reasoning the issues could not: why option C beat working the
backlog down first or tolerating red, and why the obvious objection - an exception
list is a candidate for the next blind spot - is answered by the three rules rather
than waved away. It also records what is deliberately not acknowledged, the
transient mirror divergence, because that message is the only signal if a mirror
truly stops.

One consequence is stated plainly rather than discovered later: acknowledgements
bind to substrings of the finding text, so fixing or moving a cause can change the
wording and require the entry to follow. Stable finding IDs would avoid that and
would make the file unreadable without special knowledge; the trade is taken
knowingly.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 5 5e62408a72 fix(wiki): disable the backwards push mirror, park the rest until the end
Decision sorb. The mirror from axion1337.chat/wiki to sorb/wiki is off. It pointed
the wrong way: per ADR-0015 Wiki.js writes to sorb/wiki from the cluster and
canonize_wiki pulls it back to git.lab, so this mirror would have overwritten the
wiki content with git.lab's state. It had never run once - status none, no error -
which was luck rather than design.

Whether the wiki project stays at all is deliberately not decided now. sorb wants
that conversation immediately before project completion, once production readiness
is reached on everything else, so #0105 carries it as waiting with that as its
stated reason rather than as an open task somebody might pick up.

The group check's finding is acknowledged until 2026-12-31 rather than permanently.
A permanent acknowledgement would need an ADR, and writing that ADR is exactly the
decision being postponed; a date instead means that if the project outlives it, the
question asks itself again.

Both scheduled checks now reach zero open findings.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 5 0c4cb05a0e docs(issues): #0104 - every scheduled check is permanently red
Decision sorb. AGENTS.md makes a red pipeline the alarm, with no second channel by
design. That only works while green is the normal state, and right now none of the
three scheduled checks reach it: canonize_rotation was red for nine days,
gruppenpruefung is red daily on 20 findings of which 17 are deliberately deferred,
and stillstandspruefung aborts daily for a missing GITEA_TOKEN. The management
pipeline has failed every day since at least 2026-08-12.

canonize_rotation is the proof rather than the anecdote: it failed for nine days on
a conflict touching both TURN secrets and the client image tag, and nobody noticed,
because one more red cross among red crosses is invisible. It surfaced only because
someone looked for an unrelated reason.

The issue asks how "known and deferred" gets distinguished from "new" without the
deferred work blocking the channel, and recommends acknowledging #0053's findings
with an expiry date while fixing #0031 outright. Acceptance requires showing a
freshly introduced finding still turns the pipeline red - demonstrated, not assumed.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 5 fd66b39bb6 docs(issues): #0103 - a wiki login without a group ends in silence
Decision sorb, after diagnosing why @apo could not reach the wiki. apo logs in
fine; the account exists since 2026-08-16 and was used again today. It is in no
group, and wiki-anwender has zero members, so the wiki is effectively admin-only
and every non-admin who ever tried met the same wall.

Three deliberate settings compose into a dead end: OIDC self-registration creates
the account, autoEnrollGroups is empty so it gets no group, and Guests was stripped
to no permissions while the only rule for home belongs to wiki-anwender. Default
deny then applies to every path including the start page - the user sees nothing
rather than a reason, and at the current log level nothing records the attempt
either. Same class as the mrtc outage: healthy, green, unusable, silent.

The issue asks for a decision rather than assuming one: auto-enroll into
wiki-anwender (the population is already curated by the invitation token, and
betrieb/* stays admin-only through default deny), or keep per-person curation and
make the dead end speak. Acceptance is a real login, not a config diff.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 5 577c371ebf docs(adr): supersede ADR-0002 with ADR-0019, and say what survives it
Decision sorb. ADR-0002 held that all project issues live on git.lab. Since
ADR-0012 and now ADR-0019 the canonical place is docs/issues/ in this repo, with
GitLab as the generated mirror - so that sentence stood in the record as a valid
rule next to its own opposite.

Only that sentence falls. ADR-0002 also moved the backlog repo into the lab and
named the lab the source of truth; git.lab stays canonical for code (ADR-0001) and
the repo stays where ADR-0002 put it. Because a bare "superseded" would read as if
the move were undone too, ADR-0019 now carries a section drawing the line, and the
superseded_by pointer leads a reader there. ADR-0002's body is untouched, matching
how ADR-0006 and ADR-0007 were retired.

ADR-0005 is deliberately left accepted: the WIP limit, the status labels and the
rule that only sorb promises work all still hold - only "the board is the truth"
moved, and ADR-0012 already records that.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 5 f8c2621607 docs(issues): close #0042 - the schedule step was already satisfied
Steps 2 and 4 were done today with the first mirror run and the milestone on
gitops#61. Step 3 turned out to need nothing: gruppenpruefung carries the same
schedule rule as stillstandspruefung, and the single daily schedule therefore runs
both. Verified on pipeline 472 rather than inferred - both jobs ran, both ended
red, which is the alarm doing its job.

What that leaves is a naming trap worth stating: the schedule is called
"Stillstandsprüfung (täglich)", so nobody looking for the group check finds it
there, and disabling the schedule for one reason silently disables the other check
too. Renaming is a click and belongs to sorb.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 5 6fb3cb7fed docs: relevance pass over the backlog - close #0055, retire four stale issues
#0055 is done: ThreadNet-Web be323ed checks in an .npmrc binding @sorb to rohana.
The bump that mattered was not the file but what upstream's .gitignore does with
it - it ignores /.npmrc, so the naive fix would have stayed local while CI kept
resolving against npmjs. Measured in an isolated tree: without the file pnpm goes
to npmjs and fails, with it the scope resolves to rohana at the integrity hash
the lockfile already carries, and with rohana unreachable the install fails
instead of falling back. threadnet-call only publishes and already sets the scope
in its own CI; gitops never touches it. ThreadNet-Web was the only consumer.

Four issues no longer describe reality, each verified rather than assumed:

- #0091 (gitops#61) was fixed when it was written - on_conflict: fail shipped in
  ef04d86 and the MAS pod has run that config since 2026-08-11T14:08:41Z. Its one
  deliberate remainder became #0043, which is closed and verified live.
- #0079 (gitops#46) asked for the Gitea migration and a central view. The
  migration ran; the central view was decided the other way round - repo canonical,
  GitLab mirrored (ADR-0012/0019) - which also answers the reachability trade-off
  it left open, and better than its three options did.
- #0075 (gitops#40) is rejected, not done: it wanted new issues to appear in the
  Gitea kanban automatically. Issues no longer live in Gitea and the board is
  script-written. Nothing was accomplished; the question dissolved.
- #0098 is a rollout record whose only remainder, the macOS build, is #0022.

Three AARs move to harvested - every open item in them is tracked as an issue.

Checked and still accurate, so left alone: the wiki branch still exists on both
remotes (#0019), docs/TASKS.md and oldwiki/ are still there (#0085),
element-web-docs still names live resources (#0086), res/themes/element persists
(#0100), only WIKI_CANONIZE_TOKEN is set so TURN rotation still lacks its token
(#0084), gameserver still has zero push mirrors (#0032), the broken .6 package is
still published (#0101), and options.ts still builds simulcast layers regardless
of codec, which is what blocks VP9 (#0057).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 5 f092e60dfb docs(issues): #0102 — DMARC of the platform zone is p=none and belongs to IONOS
Decision sorb: the mail hardening carried over from the mrtc diagnosis becomes an
issue. Writing it up corrected the premise the AAR and dns-soll.md carried:
#0006 hardened axion1337.DE, the zone with the real mailboxes, and it stands at
p=reject. The platform zone .chat was never its subject and still resolves its
_dmarc as a CNAME into IONOS' shared p=none - so the policy for our own domain is
set by IONOS, and RFC 7489 passes that none down to all seven service names.

Two senders are documented rather than assumed: Authentik as gamemaster@ via
IONOS SMTP (DKIM-covered), and maintenance-notify as wartung@ over an msmtp
config whose provider the template leaves open. That second path is why the issue
puts "clarify the senders" ahead of any policy change - p=reject before that
question is answered breaks maintenance mail silently.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 5 a1def8666e feat(issues): adopt the component trackers — one backlog, one numbering (ADR-0019)
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>
2026-08-18 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 5ec4aa702e docs(aar): mrtc DNS outage AAR; #0053 takes the real-timestamp commits too
The five-day group-call outage from the deleted mrtc A record had no management
record at all - the fix, the diagnosis path, and the lesson lived only in the
session. The AAR records why nothing alarmed (DNS-01 certs and pods stay green
without an A record), the exact dating via token-vs-join counts, and the
countermeasure that already shipped (notfallhandbuch dns-soll.md + pruefe-dns.sh).

gruppenpruefung's nine real-timestamp findings join #0053's history pass -
same class, same decision, recorded so the next session repairs nothing
unilaterally.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 98f49276ff docs(issues): close #0054 — AI noise suppression shipped and verified
Decision sorb: the filter is live in v0.5.4, proven in real calls on both
engine families, gated for rollback, and regression-tested on all three silent
failures found along the way. The phone test stays deliberately deferred; if it
becomes necessary it is a new issue, not a reopen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-17 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 78228e4d3b docs(issues): #0055 — the @sorb scope is pinned nowhere but the lockfile
ThreadNet-Web resolves @sorb/threadnet-call-embedded from rohana only because
pnpm-lock.yaml pins the full tarball URL and CI installs frozen. There is no
.npmrc anywhere, so the moment someone bumps the version, pnpm reaches for
registry.npmjs.org instead. Hit while bumping to .8 for #0054.

Today that fails loudly with a 404 — but only because the name happens to be
unregistered on public npm. The protection is a coincidence, not a control:
register that name and the same command resolves successfully against a
stranger's package, in the one moment where a fresh download looks expected.

Documenting it is explicitly not the fix here; the checked-in .npmrc is, because
it removes the wrong path rather than warning about it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-16 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 914eb59575 docs(adr): ADR-0018 — client-side noise suppression, opt-in and self-hosted
sorb's decision after the prototype: integrate DeepFilterNet3 as a LiveKit track
processor, off by default, assets fetched only when the user enables it, checkbox
plus slider, 35 percent default.

Opt-in is what makes the 23.3 MB affordable — only those who benefit pay for it.
Three of the source specification's assumptions did not survive measurement and are
recorded as rejected alternatives: the dry/wet mixer (the model limits attenuation
natively, and mixing raw signal back would return the keystrokes), the Rust/wasm
build (a maintained package makes it unnecessary), and loading assets from the
vendor CDN (every participant's IP to a third party at call start).

Mobile stays untested by choice; since the filter is opt-in it simply stays off on
weak devices, so that is a follow-up rather than a blocker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 a328dacc50 docs: supersede ADR-0006, open #0054 on client-side AI noise suppression
ADR-0006 (Docusaurus as the shared reading surface) is superseded by ADR-0014,
which ADR-0014 had only recorded for ADR-0007. The schema has no 'deprecated', so
superseded with a pointer is the fitting lifecycle state, same shape as ADR-0007.

#0054 evaluates an external architecture spec for filtering keyboard noise with a
WebAssembly model in the client. It holds up on diagnosis, placement and the
awkward parts (128-vs-480 sample buffering, the Chromium worklet leak, SIMD), and
it does not contradict the fork's earlier rejection of ML denoising — that one was
about the server side, for a reason that does not apply here.

It does not hold up on: a missing delay node, which would make the dry/wet mix comb
filter audibly; the premise behind dry/wet at all, since DeepFilterNet can limit
attenuation natively and mixing raw signal back in returns the very keystrokes we
want gone; PESQ figures compared across different test sets; unmeasured bundle size;
throwaway npm packages; and no mention of the standing cost of carrying this through
every upstream rebase.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 ae92952f7e docs(issues): close #0045 — reporting no longer ends in silence
Route B per sorb: Draupnir would have needed server admin to poll reports, and
bots do not get that. So reports stay in event_reports for review through Element
Admin, and the message names a person rather than promising an automatism —
@sorb being the only admin who can see them at all.

Verified live rather than assumed: the config parses, the ConfigMap carries it,
the chart hash label flipped after about 70 seconds and rolled a new pod, and the
public config.json serves the text. That rollout also confirms the #0044 analysis
empirically — no reloader needed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 0e34ec9deb docs(issues): reject #0018 as moot, close #0028 as already running
#0018 builds entirely on the Docusaurus aggregate that ADR-0014 replaced, and
sorb confirms wiki.lab is gone — measured, it resolves but answers nothing, so
the Dokploy stack it asks for was never deployed. Its one live part was step 6:
gitops still claimed the docs were served there, corrected in gitops 82412cf.

#0028 turned out to be built already, on the very path the issue proposed:
stillstandspruefung.py reads remote_mirrors and reports last_error, CI runs it on
schedule, and the git.lab schedule is active daily at 00:42 — so an expired mirror
credential goes red within a day instead of freezing production silently. It also
demonstrably fires: it flagged three repos without an active mirror today.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 de908d8307 docs(issues): reopen #0027 — W5 and W7 are unresolved again
On sorb's instruction. Six of the eight contradictions stand resolved; W5 and W7
went back to open when the unauthorised AGENTS.md edits were reverted, and both
now need a decision on whether and where the rule is recorded rather than just a
wording.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 320657b1d5 docs(issues): close #0044 — no reloader, the moving secrets are already covered
sorb's call. The goal is already met where secrets actually move: the ESS chart
rolls its components on config change via pod-template hash labels, and coturn
plus Synapse are handled by the rotation job's annotation bump — the one case with
regular unattended rotation, solved precisely because of that.

What is left are three services whose secrets change rarely and by deliberate act,
at the very moment someone is already watching and ADR-0011 applies. A permanent
controller allowed to patch arbitrary deployments is the worse trade for that.

The map is the outcome here, not an installation: the assumption had been that MAS
was uncovered, and a reloader would have been aimed at a solved problem.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 f248c4f03c docs(issues): close #0043, record the coverage map for #0044
#0043: the case-insensitive username policy is live and verified end to end —
present in the ConfigMap, mounted in the worker, applied by authentik on its own,
and bound to the prompt stage. It reads only prompt_data, since the stage runs
anonymously and that is exactly what the previous system policies died on.

#0044 turns out to be largely solved already, which the issue could not know: the
ESS chart hangs config and secret hashes on the pod template as labels, so MAS and
the other chart components do roll out on change, and coturn has its own annotation
bump driven by the rotation job. What remains are three services whose secrets
change rarely and deliberately — recommending against adding a controller for that.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 5054ad5248 docs(issues): open #0053 — carry eight non-canonical commits into the next history pass
Eight commits from the past two days carry the wrong author identity, made in an
agent session that set user.email by hand in fresh clones — an hour after that
same session wrote the canonical identity into AGENTS.md.

sorb's call is to fix them with the next history pass rather than force-pushing
two repos over eight commits. The issue exists anyway because gruppenpruefung
reports them on every run: without a recorded reason the next session starts
'repairing' them, or worse gets used to red findings, which is exactly what
happened with the TargetDown noise in #0002 the same morning.

Notes the structural prevention too — an includeIf block setting the identity for
group clones — since writing the rule down demonstrably did not prevent breaking it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 9aea4ad69d docs(issues): close #0052 — no :latest left, coturn pinned and verified
The cluster was running coturn 4.10.0 while :latest pointed at 4.17.2, which is
the concrete harm the issue describes: nobody knew what ran, a reschedule would
have jumped seven minor versions unannounced, and the CVE scan was measuring a
moving target. Now pinned to 4.17.2 and verified beyond 'the pod is up' — a STUN
binding request from the public internet succeeds and the server reports the
caller's external address, so the relay path itself is proven.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 0bede0080f docs(issues): close #0035-#0039 — group-rules pointers rolled out
All five components now carry the pointer the check looks for, verified by
gruppenpruefung.py dropping from 27 to 23 findings — exactly the four pointer
findings. Each AGENTS.md carries only what is specific and easy to get wrong
there: for the forks, that the README is upstream material describing something
else entirely; for thread-net-git, that its small compose file hosts the Flux
source; for threadnet-operating, the two lessons this session paid for.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 efd63b17c4 docs(issues): close #0027 — all eight contradictions resolved
W4 point 4 closed with gitops 60aaf0e: the lab WireGuard config now has a repo
home. The root CA turned out to already have one (ci/lab-ca-chain.crt is exactly
the aXionLabs chain), so that half of the point was quietly already met.

All eight now carry a named resolution with a reference, two of them as their own
ADRs, honouring this issue's own rule of documenting rather than silently fixing.
The only thing left is the rotation, which is dated follow-up work in #0015 rather
than an open contradiction.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 5df37eaf9d docs(issues): defer token rotation to platform acceptance (#0015, #0027 W4)
sorb's call: rotate everything once at acceptance rather than piecemeal now.
That is the lower-risk order — the mirror credential is still unidentifiable and
the mirrors feed the Flux source, so four separate revocations would mean four
separate ways to break it silently. The inventory and the ordering stay valid, so
the later rotation is execution rather than analysis.

Recorded what the deferral accepts rather than leaving it implicit: the exposed
WireGuard key and PATs stay valid, five never-used tokens remain (one with
manage_runner and k8s), and 'acceptance' is not a dated milestone — which is
exactly how security work rots. The existing due date stays as a review anchor,
not a rotation deadline.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 463fb570d5 docs: token inventory for #0015, resolve W5, sharpen W4
Inventoried the 24 git.lab PATs by metadata only — last_used_at separates
'needed' from 'lying around': four are in active use, five are active but never
used at all (one with manage_runner and k8s scope), and several names exist twice
because a replacement was created without revoking the old one. All six push
mirrors are healthy, but GitLab masks both parts of the mirror URL, so the
credential remains unidentifiable — and it is a Gitea token, which the PAT list
cannot answer for. Hence the ordering: set a dedicated mirror credential first,
revoke second. The revocations themselves are sorb's; from here a never-used
token is indistinguishable from a staged one.

W5 resolved: the secrets rule now has a bootstrap exception, since on a headless
host it was only satisfiable by violating it. W4 splits — point 5 is #0015 (plus
the WG key, which no token inventory covers), point 4 is demonstrably undone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 f433fc2b2a docs(adr): ADR-0017 — split-DNS as-built, each zone justified by measurement
Corrects only the split-DNS line of ADR-0004 (frozen once accepted, hence a
separate ADR). Rather than documenting 'four zones exist', it measures what each
one does: ~lab and ~axionlabs.de resolve names that exist only internally or
differently (git.lab, and ca.axionlabs.de as real split-horizon to the step-ca),
~axion1337.de carries the internal-only git.axion1337.de, and ~lab.de carries
nothing at all while routing a foreign public domain through the lab resolver —
so it goes.

This also answers the audit's rollback option: reverting to ~lab alone would have
broken internal CA and git resolution. The purpose was never written down, which
is why rolling back would have been blind.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 51c05edb2d docs(issues): review all waiting issues, close #0041 and #0025
Went through the seven imported waiting issues and replaced the generic
'reason is in the GitLab history' placeholder with the real blocker, which
completes #0041. Three of the seven were not merely imprecise but wrong:

- #0025: the deploy had long landed; screenshots confirm 24 aggregated messages
  in the security room (limit 29), summing to the known 126 CRITICALs.
- #0014: the A/B/C decision exists as ADR-0008 (option A). Half its open question
  is now answered — MATRIX has no docker group at all, so the root-equivalence
  does not apply there.
- #0027: the blocking Struktur-Workshop happened on 2026-08-06 and produced three
  ADRs, but W1 and W3 were spot-checked and are still unresolved.

The remaining four wait on a named action by sorb. Measured from here: the GAME
exporters are still filtered (and their silences expired on 2026-08-04, so
TargetDown has been firing every 4h since), while CFGMON's 9090/3100 are already
closed from the internet — so #0008 is about making that state deliberate rather
than an acute exposure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 df8dcad8e1 docs(issues): close #0025 — CVE alert deploy is live and verified
The handover issue still sat in waiting while the deploy had long landed:
ff87cb2 is an ancestor of HEAD (CFGMON now runs e9c13dc), the rules aggregate
per image so the per-CVE flood is structurally impossible, matrix-alerts.py
saves state incrementally inside the send loop, and notifications_failed_total
is 0 across 80 series. The null-receiver kill switch is gone.

Recorded honestly what was not observed: whether aggregated messages actually
arrived in the security room once. Delivery is now permanently monitored via
AlertDeliveryFailing, so a future failure reports itself instead of relying on
someone looking.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-15 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 6f74642659 docs(adr): ADR-0016 — notfallhandbuch stays lab-internal, no mirror
Standing exception to ADR-0001 (everything is push-mirrored to Gitea). The
handbook necessarily maps the infrastructure, the backup locations and where the
keys are kept; mirroring it onto the internet-facing host that is itself one of
the covered failure cases would hand a post-compromise attacker their next step.
Confidentiality over availability, with a local clone closing the availability
gap. Records the rejected alternatives so the question does not reopen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 87aab7948f docs(issues): reject #0010 (Gitea is a mirror), age key escrow resolves #0030 risk
sorb's call: Gitea on rohana is a push mirror of the canonical git.lab, so a
nightly gitea dump would back up a copy — effort not justified, cron stays off.
Documented the one non-mirror asset for the record: the container registry holds
four images the cluster pulls (incl. threadnet-web and the backup image itself),
which is rebuild time rather than data loss and is covered by #0022/#0033.

For #0030, sorb confirms the age key is also in the password vault, dissolving
the circular dependency found earlier. Noted that the vault is now part of the
restore path and must lead the procedure.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 74f4fa91f0 docs(issues): #0030 inventory done, #0010 scope reduced — age key is the real risk
Cluster-side backups are healthier than assumed: three nightly Borg jobs to a
Hetzner Storage Box, all completing with plausible volumes and working prune
(synapse 199MB/247 files, authentik ~150MB, wikijs 223kB DB-only). No silent
failures.

Critical finding for #0030: the Borg passphrase and SSH key needed to READ those
backups are SOPS-encrypted under a single age key that exists only in the cluster
being backed up and on one laptop — no documented cold copy. Losing both makes all
three repos permanently unreadable. Cold escrow must precede any restore drill.

For #0010 this shrinks the work: the Storage Box + Borg pattern already exists and
is proven, so Gitea needs only its own repo there. The disabled cron (no backups
since 2026-07-30) remains separately urgent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 c0a428f43d docs(issues): close #0006 — apex DMARC already at p=reject, DKIM present
Verified via DoH: _dmarc.axion1337.de is p=reject (sorb changed it), subdomains
inherit reject with sp= absent per RFC 7489, and the noted DKIM gap was a false
alarm — IONOS uses s1-ionos/s2-ionos/s42582890 selectors, all present with valid
keys. Apex SPF left at ~all deliberately: real mail flows over the apex and DMARC
already enforces reject, so -all adds little while risking silent send breakage.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 75e00569da docs(issues): close #0005 — IONOS zone cleanup complete
ftp.axion1337.de deleted and verified (NXDOMAIN). All four DNS hygiene issues
from today's batch (#0001, #0003, #0005, plus #0007 earlier) are now closed:
rohana and selendis hardened with Null-MX/SPF -all/DMARC reject, matrix and
www.game removed, ftp removed. Production A/AAAA records untouched throughout.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 64f11c178a docs(issues): close #0001, #0003 — matrix/game www cleanup done
matrix.axion1337.de was already removed by sorb independently (platform runs
under .chat), so www.matrix went with it (confirmed NXDOMAIN via two
independent DoH resolvers). www.game deleted through IONOS and verified.
#0005 down to a single remaining item: ftp.axion1337.de.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 f39253cf54 docs(issues): fix WIP overflow — #0001/#0003 to waiting on IONOS
Previous commit set three issues in-progress, tripping the WIP<=2 rule. The
verification is done; the DNS mutations are sorb's to run in IONOS, so #0001
and #0003 move to waiting (with wartegrund) while #0005 drives the batch.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 8f3bdc31a1 docs(issues): start #0001/#0003/#0005 — DNS zone cleanup verified
Verified current axion1337.de zone state via DoH: www.matrix/www.game are
IONOS-default records nothing serves (cluster routes .chat, no matching cert);
rohana is unhardened (no Null-MX/-all/reject) with www.rohana still present;
selendis mail-set untouched; matrix mail-set + autodiscover present; ftp is
IONOS-hosting ballast. Attached a consolidated per-name IONOS action list; the
mutations are sorb's to run in IONOS (no API access from here). Batch in-progress.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 e09ab508af docs(issues): close #0007 done — DNS-01 live and verified
Cert renewal switched to DNS-01 (IONOS): test issuance validated end-to-end
(LE YR1, valid to 2026-11-12), the shared letsencrypt resolver now renews
rohana/selendis via DNS-01, so the September renewal needs no open port 443.
Syncs the canonical file with the already-closed git.lab tracker issue 7.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 ac6202c4a1 docs(issues): start #0007 — DNS-01 change prepared, in-progress
Traefik stack is thread-net-git (manual compose deploy on CFGMON). DNS-01 diff
is ready (tlschallenge -> dnschallenge/ionos + IONOS_API_KEY via host .env).
Two human dependencies remain: create the IONOS API key and deploy+verify on
CFGMON (no SSH from here).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 8fd23274d8 docs(wiki): harvest Wiki.js stumbles; mark AAR harvested
Consolidate the 13 findings + learnings from the Wiki.js AAR into
docs/wiki/stolpersteine/wikijs.md (config/deploy, theming, navigation,
locale/timezone incl. the standing fork patch, access control, git-storage),
link it from the wiki index, and set the AAR status to harvested.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore CimbalandClaude Opus 4.8 c6ea585643 docs(issues): reject #0023 (obsolete post-Wikijs), plan #0007 cert renewal
#0023 (Docusaurus navbar logo) is moot since Wiki.js replaced Docusaurus
(ADR-0014) -> rejected. #0007 (cert renewal, due 2026-09-28) gets a concrete
plan: pursue DNS-01 (approach B, already recommended) before mid-September,
with the port-opening fallback A as a dated calendar checkpoint.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-14 12:00:00 +00:00
Thore Cimbal 1d38fa6f6d docs(issues): open #0051 CVE remediation + #0052 update cadence (M5) 2026-08-14 12:00:00 +00:00