Deep-Audit Medium-Tranche: Anchors-RPC, Dependency-Lock, to_thread, RLS-Suite, captureError + i18n-de - #13
Merged
Merged
Conversation
…nen durften (P1-2)
Deep-Audit 2026-07-12, P1-2 — zwei Defekte in collaborative_anchors,
ein Schnitt:
WARUM (1) — RLS-Lücke: Migration 129 gab der Tabelle nur Public-Read +
service_role-Policies. Die Geschwister aus derselben Migration
(bureau_responses, substrate_attunements) bekamen Editor-Write-Policies,
collaborative_anchors nicht. Die Endpunkte sind require_role("editor") +
get_effective_supabase — für jeden Nicht-Admin also User-JWT: das
Join/Leave-UPDATE traf 0 Zeilen → 500 "Failed to join anchor.", das
Create-INSERT scheiterte mit 42501. Nur auto-elevierte Plattform-Admins
konnten das Feature je benutzen (dieselbe Klasse wie der
narrative_arcs-Fund aus Migration 259).
WARUM (2) — Lost-Update-Race (ADR-007): join_anchor/leave_anchor lasen
den anchor_simulation_ids-Snapshot, hängten in Python an bzw. entfernten
und schrieben das GANZE Array zurück — ohne CAS. Konkurrierende Joins
verloren Teilnehmer; ein Leave im Rennen mit einem Join konnte den Anker
fälschlich auflösen oder wiederbeleben.
WAS:
- Migration 271: fn_anchor_join / fn_anchor_leave (plpgsql, SECURITY
INVOKER) — Dedup-Append, Status-Guard und Dissolve-Entscheidung in
EINEM UPDATE, Fehlklassifikation ('not_found'/'not_accepting'/
'already_member'/'not_member') aus dem Post-UPDATE-Zustand, damit der
Service seine unterscheidbaren HTTP-Fehler behält. EXECUTE nur für
service_role (REVOKE anon/authenticated, pg_default_acl-Hygiene wie
257/258). Muster: fn_arc_attach_event (Migration 259).
- Editor-INSERT-Policy für create_anchor: der Aufrufer darf nur als er
selbst, nur für eine von ihm editierte Simulation und nur mit exakt
dieser Simulation als Seed-Array anlegen ((SELECT ...)-gewrappt per
initPlan-Regel, Migration 183). create bleibt damit normales
RLS-geprüftes CRUD auf dem User-Client — kein service_role für CRUD.
- AnchorService.join_anchor/leave_anchor rufen die RPCs über den
Admin-Client; der Parametername `admin` erzwingt die Signatur an jeder
Callsite (P1-1-Muster). Router reicht get_admin_supabase für den RPC
durch, Audit-Log bleibt auf dem Effective-Client.
VERIFIKATION:
- Migration lokal via psql angewendet; SQL-Smoke-Test aller acht
Outcome-Pfade (joined/already_member/not_accepting/left/not_member/
not_found/dissolved-Status/Array-Inhalt) grün.
- Neue Integrationssuite backend/tests/integration/test_anchor_rpcs.py:
Outcome-Matrix über den Service, 8 konkurrierende Joins ohne
Teilnehmerverlust, anon-Ausschluss der RPC-Surface via PostgREST —
7 passed.
- scripts/lint-no-secdef-public-grant.sh gegen die migrierte lokale DB:
OK. ruff clean; Heartbeat/Anchor-Subset 17 passed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… scannte (P1-4) Deep-Audit 2026-07-12, P1-4 — Dependency-Single-Source wiederhergestellt. WARUM: Das Dockerfile installierte backend/requirements.txt (58 Pins, null Hashes, händisch gepflegt), während CI und pip-audit gegen `pip install -e ".[dev]"` aus pyproject liefen. Verifizierter Widerspruch: fastapi==0.135.1 shipped vs. >=0.136.0 deklariert — 0.135.1 erfüllt die Range nicht einmal. Der Mai-Hash-Lock (WP-6a/6b) war verschwunden. Folge: CI-grün ≠ Prod-funktioniert, und das tatsächlich ausgelieferte Set wurde nie auf Vulnerabilities gescannt (pip-audit fand im alten Set live: pyjwt 2.12.0 PYSEC-2026-178 + python-multipart 0.0.22 mit fünf PYSECs). WAS: - backend/requirements.txt ist jetzt ein uv-kompiliertes Hash-Lock aus pyproject.toml (Single Source of Truth): `uv pip compile pyproject.toml --universal --python-version 3.13 --generate-hashes` — 77 Pakete, 2028 SHA256-Hashes, byte-reproduzierbar (verifiziert: Doppellauf identisch). Einmalig mit --upgrade auf frische Versionen gehoben — pyjwt 2.13.0, python-multipart 0.0.32, fastapi 0.141.1; pip-audit gegen das neue Lock: "No known vulnerabilities found". - pyproject präzisiert: pydantic-ai → pydantic-ai-slim[openai]. Das Backend importiert ausschließlich Agent/exceptions/models.openai/ providers.openai (OpenRouter spricht das OpenAI-Protokoll); das Voll-Paket zog ~78 ungenutzte Pakete nach (anthropic, google-genai, mcp, logfire, opentelemetry, keyring, …). Prod shippte ohnehin schon slim[openai] — jetzt sagt die Deklaration die Wahrheit. - Dockerfile: pip install --require-hashes — ein manipuliertes oder unge-hashtes Paket bricht den Build ab. - ci.yml: neuer Job lock-backend — rekompiliert das Lock mit exakt dem Header-Kommando (uv gepinnt auf 0.11.16, Output-Format muss reproduzierbar sein) und failt bei Divergenz. uv übernimmt dabei committete Pins, solange sie pyproject erfüllen — ein neues Upstream-Release bricht den Build also NICHT, nur eine pyproject-Änderung ohne Lock-Regenerierung oder ein handeditiertes Lock. - security.yml: pip-audit zusätzlich gegen das Shipped-Set (--no-deps -r backend/requirements.txt — das Lock ist vollständig, auditiert wird exakt das, was das Docker-Image installiert); der bisherige Dev-Env-Audit bleibt daneben bestehen. VERIFIKATION: - Frisches Venv NUR aus dem Lock (--require-hashes): App-Import OK (`from backend.app import app`). - pip-audit --no-deps gegen das Lock: keine bekannten Vulnerabilities. - Rekompilierung byte-identisch (CI-Divergenz-Check ist stabil); uv 0.11.16 auf PyPI verifiziert (HTTP 200). - Lokales .venv auf denselben Stand gehoben; volle Backend-Suite auf dem neuen Stack (pydantic-ai-slim 2.21.0, fastapi 0.141.1, pyjwt 2.13.0): 3427 passed, 179 skipped, 0 failed. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vent-Loop (P1-6) Deep-Audit 2026-07-12, P1-6 — asyncio.to_thread-Sweep über die Image-/Shapely-Pipelines. WARUM: Ein einziger uvicorn-Loop bedient sämtliches HTTP und alle Scheduler. Jeder AVIF-Encode, jede 1080×1350/1080×1920-PIL-Komposition und der Shapely-Voronoi-/Straßenbau liefen synchron AUF dem Loop — 0,1-5 s Stillstand pro Aufruf, bei einer Forge-Materialisierung dutzendfach gestapelt, beim Instagram-Scheduler-Tick zusätzlich im Hintergrundtakt. Präzedenz für den Fix existierte im Repo bereits (email_service, translation_service nutzen asyncio.to_thread). WAS (alle Funktionen sind pure bytes-in/bytes-out, mechanische Wraps an der Async-Grenze): - forge_image_service: 2×2 convert_to_avif (Lore-Upload + Upload-Paar Full/Thumb) → to_thread. - dungeon/showcase_image_service: beide convert_to_avif → to_thread. - style_reference_service: _convert_to_avif → to_thread. - instagram_image_service: die drei async Feed-Composer (agent_dossier, building_surveillance, bureau_dispatch) wrappen _compose_with_overlay jetzt in to_thread. - social_story_service: alle fünf Story-Composes an der Async-Grenze gewrappt — detection/advisory (sync Helper) am Dispatch in compose_story_image, classification/impact/subsiding in ihren async Helpern. Damit läuft auch instagram_story_composer (volle 1080×1920-Canvas mit Blur/Glow) im Worker-Thread. - external/bluesky: Der Inline-Resize+Recompress-Block in upload_media wurde zum sync Helper _resize_recompress_jpeg extrahiert und via to_thread aufgerufen. Das bisherige _recompress_jpeg war toter Duplikat-Code (einzige Referenz: die eigene Definition) und ist durch den neuen, tatsächlich genutzten Helper ersetzt. - forge_map_service: der Preset-Generator (Shapely-Voronoi/Straßen, Teil einer dokumentierten ~30-s-Operation) → to_thread. VERIFIKATION: ruff clean; betroffene Test-Subsets (bluesky, instagram, forge_map, social_story, style_reference, showcase, forge_image, story): 172 passed. Kein Test referenzierte das entfernte _recompress_jpeg. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…gar nichts (P1-8)
Deep-Audit 2026-07-12, P1-8 — echte RLS-Verifikation gegen die lebende
Datenbank plus Tests für den einzigen Map-Geometrie-Schreibpfad.
WARUM: test_rls_policies.py mockte die Datenbank vollständig — die
Prämisse im Docstring ("cannot run against a real Supabase in CI") ist
seit langem stale, CI fährt echtes Supabase. Kern-Tabellen-RLS (agents,
simulations, platform_settings, Journal, Resonance, Epoch) hatte null
automatisierte Verifikation; eine kaputte Policy-Migration ging grün
durch CI. Echte anon-RLS-Checks existierten nur für DRIFT-Tabellen.
fn_apply_map_geometry (Migrationen 236/245) — dessen gesamte
Existenzberechtigung der atomare All-or-Nothing-Kontrakt ist — hatte
null Tests.
WAS:
- NEU test_rls_matrix.py (DRIFT-Stil: rohe PostgREST-Requests mit
anon-Key, echte User-JWT-Clients, service_role nur für Seed/Teardown):
* Public-First-Reads: simulations/agents/substrate_resonances/
game_epochs anon-lesbar (geseedete Wegwerf-Zeilen sichtbar;
Erkenntnis eingearbeitet: die anon-Policies exponieren nur
status='active'-Simulationen).
* Geschützte Reads: platform_settings (trägt API-Keys) leakt weder an
anon noch an authenticated; journal_fragments owner-scoped — anon
und FREMDE authentifizierte User sehen nichts, der Owner sieht seine
Zeile.
* Anon-Writes: INSERT auf jede Kern-Tabelle wird abgelehnt — geprüft
über Status UND Abwesenheit der Marker-Zeile danach (immun gegen
400-vs-403-Ambiguität).
* SECDEF-RPC-Surface: fn_apply_map_geometry, fn_apply_zone_adjacencies,
fn_auto_draft_participants, fn_compute_cycle_scores für anon UND
authenticated unerreichbar (ADR-006); fn_update_user_byok_keys
weist Cross-User-Calls mit echtem User-JWT ab. Implementiert damit
den skipif(True)-Stub, der seit Migration 128 in
test_auth_boundaries.py §1.6 auf "when running against real
Supabase" wartete — ersatzlos entfernt, Verweis hinterlassen.
- NEU test_apply_map_geometry.py: Happy Path (alle Writes landen,
Counter-Objekt exakt, Version +1, Seed persistiert, Straßen-Set voll
ersetzt, deterministisches lives_at, Draft → map_status='succeeded')
und Rollback (vergifteter Street-Eintrag: Zone-UPDATE und
Street-DELETE liefen VOR dem scheiternden INSERT — alles muss
zurückrollen: Version/Seed unangetastet, alte Straße wieder da, kein
lives_at, Draft bleibt 'generating').
Fixture-Detail: forge_drafts.user_id löst sich bevorzugt über eine
existierende user_profiles-Zeile auf, damit die Suite auch lokal
läuft (GoTrue-Admin-API lokal 503 — bekannte Limitation); auf
jungfräulicher CI-DB Fallback auf die geteilten Testuser (lazy via
request.getfixturevalue, der Skip greift nur wenn wirklich nötig).
- test_rls_policies.py → test_dependency_gates.py umbenannt: der Inhalt
(FastAPI-Dependency-Gates, gemockt) ist wertvoll, aber kein RLS —
der Name log. Docstring entsprechend neu.
- test_concurrent_scenarios.py GELÖSCHT: sechs pytest.skip-Platzhalter
ohne eine einzige Assertion — toter Coverage-Signal-Ballast. Die
realen Szenarien sind längst anderswo abgedeckt: Epoch-Advance/
Resolve-Contention (test_race_conditions, test_epoch_concurrency),
RP-Spend/Join-Races (test_epoch_concurrency), Anchor-Join-Races
(test_anchor_rpcs, dieser Branch).
VERIFIKATION: Alle JWT-abhängigen Erwartungen zusätzlich DB-seitig per
psql-Rollensimulation (SET role/request.jwt.claims) bestätigt:
platform_settings 0 Zeilen für authenticated, System-RPC permission
denied, byok-RAISE, Journal-Owner 1/Fremd 0. Lokal: 148 passed,
9 skipped (User-JWT-Tests, lokale Auth-Limitation — laufen in CI).
ruff clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ry trotzdem blind (Audit-Sweep)
Deep-Audit 2026-07-12, Punkt 5 der Medium-Tranche — captureError-Sweep
plus die Lint-Regel, die den Kontrakt dauerhaft in CI verankert.
WARUM: lint-no-empty-catch.sh prüfte nur, OB ein Binding existiert
(Regeln 1+2). `catch (err) { console.error(err); }` passierte den Check
und ließ Sentry trotzdem blind — der Audit fand ~50 solcher Sites,
darunter 8 im ForgeStateManager und die Kommando-Pfade des
BureauTerminals (Command-Fehler landeten nur in der Browser-Konsole).
Die beiden Basisklassen (DataLoaderMixin, BaseSettingsPanel) waren
bereits in der Quick-Win-Tranche gefixt (QW5).
Dabei entdeckt und mitgefixt: CI und `npm run lint:full` rufen das
Skript vom REPO-Root auf — dort matchte das nackte `src/` der Greps
nichts, und `2>/dev/null || true` machte die Regeln 1+2 zu stummen
No-Op-Pässen. Das Skript verankert sich jetzt selbst am Frontend-Root
(SCRIPT_DIR vor dem cd auflösen — BASH_SOURCE kann relativ sein) und
ist damit aus jedem Arbeitsverzeichnis korrekt.
WAS:
- NEU scripts/lint-catch-observability.mjs (Regel 3): parst jeden
`catch (ident) { ... }`-Block und jeden `.catch((ident) => ...)`-
Handler per string-/kommentar-bewusstem Brace-Matching (ein `}` in
einem Template-Literal beendet keinen Block) und verlangt
captureError-or-throw im Body. Allowlist: SentryService.ts (IST der
Capture-Service). In lint-no-empty-catch.sh als Regel 3 verdrahtet →
läuft in CI und lint:full.
- 48 Sites gefixt: captureError(err, { source: 'ClassName.methodName' })
als erstes Statement im catch-Body, bestehendes Verhalten (Fallbacks,
Toasts, State) unverändert; 12 Dateien bekamen den fehlenden Import.
Betroffen u. a.: ForgeStateManager (8×), alle 6 Settings-Panels,
BureauTerminal (3×), 5 Admin-Tabs, Chat/Chronicle/Dungeon/Health/
Multiverse/Agents-Komponenten, ChatAudioService, ChatSessionStore,
terminal-commands.
- Bewusste Nuance in ChatWindow._handleRegenerate: der catch silenced
absichtlich AbortError (regulärer Stream-Abbruch bei Navigation);
captureError sitzt im Nicht-Abort-Zweig — sonst würde jeder gewollte
Abort als Produktionsfehler gemeldet.
VERIFIKATION: node scripts/lint-catch-observability.mjs → PASS (0
Verstöße, vorher 48); `npm run lint:full` komplett grün (typecheck,
biome, alle 8 Shell-Lints inkl. der reparierten Regeln 1+2).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…er Deutsch Nachzug der deutschen Übersetzungen für alle seit dem letzten Locale-Build hinzugekommenen msg()-Strings — v. a. DRIFT W1/W2 (PR #11), Alpha-Suite, Bureau Ops, Content-Draft-Editor und die Quick-Win-Tranche (PR #12). KLARSTELLUNG (User-Korrektur dieser Session): Dieses Repo lokalisiert ausschließlich en → de (lit-localize, ein einziges de.xlf). Die im Ledger notierten Locales »de-lit«/»swag« existieren hier nicht — der Auftrag reduziert sich auf das deutsche Target. WIE: lit-localize extract → 796 Einheiten ohne <target>. Übersetzung via OpenRouter (anthropic/claude-sonnet-4-6 — der Haus-Standard des TranslationService; lokal liegt kein DeepL-Key) in 20 Batches, mit Bestandsterminologie als verbindlichem Glossar (Bleed bleibt Lehnwort, Shard=Scherbe, Epoch=Epoche, Anchor=Anker, Whisper=Flüstern, Clearance=Freigabe, du-Form, Halbgeviertstriche). <x>-Platzhalter wurden vor der Übersetzung tokenisiert (⟦n⟧) und validiert wieder eingesetzt — Multiset-Gleichheit pro Einheit erzwungen. Bereits deutsche Flavor-Quellen (Alpha-Suite: »Vorabübertragung«, »Erstkontakt« …) unverändert durchgereicht. 3 Batch-Ausreißer von Hand übersetzt. Gotchas aus dem Memory beachtet: ET.register_namespace VOR dem Parsen (kein ns0:-Namespace-Schaden — verifiziert), lit-localize build + &-sed-Fix auf generated/de.ts. VERIFIKATION: 7601/7601 trans-units mit <target> (0 offen); Namespace im File-Header intakt; 0 Geviertstriche im generierten de.ts; Stichproben (Placeholder-Einheiten, Merge-UI, Bureau Ops) geprüft; npm run lint:full komplett grün (typecheck, biome, alle Shell-Lints). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Deep-Audit 2026-07-12 — Medium-Tranche (Punkte 1–5) + i18n-Nachzug
Fortsetzung von PR #12 (Quick-Win-Tranche). Sechs Commits, je einer pro Häppchen; jedes Finding vor dem Fix gegen den aktuellen Baum re-verifiziert.
P1-2 · collaborative_anchors: atomare Join/Leave-RPCs + fehlende Editor-Policy (Migration 271)
Migration 129 gab der Tabelle nie eine authenticated-Write-Policy — Join/Leave/Create waren für alle Nicht-Admins kaputt (0-Rows-UPDATE → 500, INSERT → 42501). Zusätzlich ADR-007-Race im Python-Read-Modify-Write aufs UUID-Array. Fix im Muster von Migration 259:
fn_anchor_join/fn_anchor_leave(ein UPDATE, Guard in der WHERE-Klausel, service_role-only) + Editor-INSERT-Policy für create (bleibt User-Client-CRUD). Neue Suitetest_anchor_rpcs.pyinkl. 8-fach-Concurrent-Join ohne Teilnehmerverlust und anon-Surface-Check.P1-4 · Dependency-Single-Source
requirements.txt(58 Pins, 0 Hashes, stale —fastapi==0.135.1vs. deklariert>=0.136.0) ist jetzt ein uv-kompiliertes Hash-Lock aus pyproject (77 Pakete, 2028 Hashes, byte-reproduzierbar). Dockerfile:--require-hashes. Neuer CI-Joblock-backend(uv gepinnt; übernimmt committete Pins, solange sie pyproject erfüllen — neue Upstream-Releases brechen den Build nicht). security.yml auditiert zusätzlich das Shipped-Set; das alte trug live-Vulns (pyjwt 2.12.0, python-multipart 0.0.22). pyproject präzisiert:pydantic-ai-slim[openai]statt Voll-pydantic-ai(~78 ungenutzte Transitiv-Pakete weniger; nur Agent/exceptions/models.openai/providers.openai werden importiert).P1-6 · asyncio.to_thread-Sweep
Alle acht CPU-Sites von der Event-Loop geholt: forge_image (2×2 AVIF), showcase (×2), style_reference, instagram_image (3 Feed-Composer), social_story (alle 5 Story-Composes), bluesky (Inline-Block →
_resize_recompress_jpeg; totes_recompress_jpegentfernt), forge_map (Shapely-Generator).P1-8 · Echte RLS-Matrix-Suite + fn_apply_map_geometry-Tests
test_rls_matrix.py: Public-First-Reads (anon), platform_settings-/journal_fragments-Leak-Checks (anon + fremde authenticated User), Anon-Write-Matrix (Status UND Marker-Row-Abwesenheit), SECDEF-RPC-Surface (anon + authenticated), byok-Cross-User-RAISE. Implementiert den skipif(True)-Stub aus test_auth_boundaries §1.6.test_apply_map_geometry.py: Happy Path (Counter exakt, Version +1, Straßen-Set voll ersetzt, deterministisches lives_at, Draft → succeeded) + Rollback-Beweis (vergifteter Street-Eintrag: auch der vorangegangene Zone-UPDATE und Street-DELETE rollen zurück).test_rls_policies.py→test_dependency_gates.py(Inhalt = Dependency-Gates, kein RLS — der Name log);test_concurrent_scenarios.py(6 Skip-Stubs ohne Assertion) gelöscht.captureError-Sweep + Lint-Regel 3
48
catch (err)-Sites, die den Fehler banden, aber nie beobachteten, auf den Haus-Kontrakt gebracht. NEUlint-catch-observability.mjs(Brace-Matching, captureError-or-throw im Body) als Regel 3 inlint-no-empty-catch.sh. Dabei entdeckt und gefixt: das Skript lief in CI vom Repo-Root, wodurch diesrc/-Greps der Regeln 1+2 stumme No-Ops waren — jetzt selbst-verankernd.i18n(de) · 796 neue msg()-Strings übersetzt
Klarstellung aus der Session: das Repo lokalisiert nur en→de. Übersetzung via OpenRouter (claude-sonnet-4-6, Haus-Standard) mit Bestandsterminologie-Glossar (Bleed=Lehnwort, Shard=Scherbe, du-Form),
<x>-Platzhalter tokenisiert+validiert, deutsche Flavor-Quellen durchgereicht. 7601/7601 Einheiten mit Target, 0 Geviertstriche.Verifikation
npm run lint:fullkomplett grün (typecheck, biome, alle Shell-Lints inkl. reaktivierter Regeln 1+2).from backend.app import appOK; pip-audit clean.🤖 Generated with Claude Code