2e9d3706ec
Testy / Testy warstwy logicznej (silnik) (push) Failing after 4m50s
Testy / Testy warstwy prezentacji (dostęp do baz) (push) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (push) Successful in 9m25s
Testy / Testy astrodemo (push) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (push) Successful in 8s
Testy / Kontrola składni wszystkich warstw (push) Successful in 5s
Testy / Testy warstwy logicznej (silnik) (pull_request) Failing after 4m43s
Testy / Testy warstwy prezentacji (dostęp do baz) (pull_request) Successful in 9m28s
Testy / Testy warstwy bazodanowej (ochrona baz) (pull_request) Successful in 9m25s
Testy / Testy astrodemo (pull_request) Successful in 9m25s
Testy / Build obrazu silnika B (swisseph) (pull_request) Successful in 6s
Testy / Kontrola składni wszystkich warstw (pull_request) Successful in 4s
Pierwszy z pięciu kroków budowy trzech produktów: astrodemo (dwie funkcje) → astroklient (pełne astro bez AI) → astrololo (wszystko). Nazwa „astroklient" zostaje zwolniona dla warstwy pośredniej, więc dotychczasowe astroklient-demo nazywa się teraz astrodemo. REBASE. Cztery commity demo przeniesione na aktualnego mastera. Konflikt był jeden — rejestr wymagań (xlsx, binarny, git go nie scali). Master dodał LOG-34, gałąź demo PRE-28 i PRE-29; żaden wspólny wiersz się nie różnił, więc scalone ręcznie: 148 pozycji, wszystkie trzy obecne. ZMIANA NAZWY. Katalog, ciasteczko sesji (astrodemo_sesja), zmienne ASTRODEMO_USERS/USER/PASSWORD, CI, README, docstringi. Ponieważ PR z demo nigdy nie został zmergowany, usługa nie jest nigdzie wdrożona — zmiana nazw niczego nie migruje i nikogo nie wylogowuje. WYCIEKI. astrodemo powstało przed audytem z PRE-27, więc miało komplet tych samych dziur: - /static omijało bramkę (PUBLIC_PREFIXES), a pierwszy komentarz w styles.css brzmiał „Nie kopiujemy stylów pełnej aplikacji" — czyli anonimowy curl dowiadywał się, że istnieje pełna aplikacja. Zasoby idą teraz trasą z jawną listą, komentarze są zdejmowane przy serwowaniu. - Komunikat awarii wypisywał na ekran treść wyjątku httpx, z nazwą usługi i portem. Teraz jedno neutralne zdanie, szczegóły do dziennika. - /health oddawał nazwę warstwy. Teraz samo „ok". - Dziesięć komentarzy i docstringów tłumaczyło decyzje przez porównanie z „pełną aplikacją". Obraz tej usługi się KOMUŚ ODDAJE, więc kto go dostanie, przeczyta też komentarze. Przepisane tak, żeby opisywały tę usługę samą w sobie. - Nagłówek main.py twierdził, że demo dzieli pulę plików z produkcją. To nieprawda od PRE-29 (pule per konto) — opis poprawiony. ZAPORA SŁOWNIKOWA, dwupoziomowa. Poziom „wszędzie" (także w kodzie serwera, bo obraz się oddaje) obejmuje wzmianki o większym rodzeństwie, o modelu językowym i o funkcjach, których tu nie ma. Poziom „do przeglądarki" dokłada słownictwo mechanizmów. Test sprawdza odpowiedzi ORAZ drzewo plików. Jeden wyjątek jest jawny i opisany: stałe protokołu łącza (X-Astrololo-Token, X-Astrololo-Enc, typ treści, etykieta HKDF) niosą nazwę rodziny produktów. Są wspólne z warstwą logiczną, więc zmiana wymaga jednoczesnej podmiany we wszystkich usługach i rotacji — osobna decyzja. Osobny test pilnuje warunku, pod jakim to zostaje: że nie docierają do przeglądarki. Wcześniej przechodziły tylko dlatego, że regex nie dopasowywał po myślniku — przypadek, nie decyzja. Przy okazji: komunikat „ramka bez znacznika astrololo" zmieniony na neutralny we WSZYSTKICH PIĘCIU kopiach link_crypto.py (presentation, astrodemo, logic, data, render), żeby nie rozjechały się przed scaleniem w rdzeń. Te kopie to 2625 linii tego samego kodu. Testy: astrodemo 27, presentation 358, logic 342, data 42, render 41. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
80 lines
3.6 KiB
Python
80 lines
3.6 KiB
Python
"""Klient warstwy logicznej — CELOWO okrojony do dwóch funkcji (PRE-28).
|
|
|
|
Ma tu być wyłącznie to, czego ta usługa naprawdę używa. Metoda, której nie ma,
|
|
nie zostanie przypadkiem zawołana ani nie podpowie, że po drugiej stronie jest
|
|
coś więcej.
|
|
|
|
Rozmowa idzie tym samym szyfrowanym łączem co reszta (PRE-16) i pod tym samym
|
|
tokenem międzywarstwowym (LOG-32) — demo nie jest furtką omijającą ochronę.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
from typing import Any
|
|
|
|
import httpx
|
|
|
|
from app import link_crypto
|
|
from app.config import settings
|
|
|
|
|
|
def _auth_headers() -> dict[str, str]:
|
|
token = os.getenv("INTERNAL_TOKEN", "")
|
|
return {"X-Astrololo-Token": token} if token else {}
|
|
|
|
|
|
def _link() -> link_crypto.Link | None:
|
|
"""Klucz łącza czytany przy KAŻDYM wywołaniu — pod przeżywa rotację sekretu
|
|
bez restartu, a brak klucza przy wymaganym szyfrowaniu ma zatrzymać żądanie
|
|
ZANIM cokolwiek wyjdzie jawnym tekstem."""
|
|
key = link_crypto.key_from_env(link_crypto.ENV_PRESENTATION_LOGIC)
|
|
return link_crypto.Link(key) if key else None
|
|
|
|
|
|
class LogicClient:
|
|
"""Rozmówca warstwy logicznej ZWIĄZANY Z PULĄ KONTA (PRE-29).
|
|
|
|
Pulę podaje się przy tworzeniu klienta, a bierze się ją WYŁĄCZNIE z loginu
|
|
zalogowanej osoby — nigdy z pola formularza. Gdyby przychodziła z żądania,
|
|
wystarczyłoby podstawić cudzy login, żeby czytać cudze bazy."""
|
|
|
|
def __init__(self, tenant: str = "", base_url: str | None = None) -> None:
|
|
self.base_url = (base_url or settings.logic_url).rstrip("/")
|
|
self.tenant = tenant or ""
|
|
|
|
def report(self, when_utc_iso: str, lat: float, lon: float) -> dict[str, Any]:
|
|
"""Interpretacja urodzeniowa: sygnifikatory z obliczeń szukane w bazach.
|
|
|
|
Jedyne pytanie, jakie astrodemo zadaje o treść — bez grupowania,
|
|
bez grupowania i bez żadnej obróbki treści."""
|
|
payload = {"when_utc": when_utc_iso, "lat": lat, "lon": lon,
|
|
"limit": settings.report_limit, "group": False,
|
|
"tenant": self.tenant}
|
|
with httpx.Client(timeout=settings.http_timeout) as client:
|
|
return link_crypto.call_json(client, "POST", f"{self.base_url}/chart/report",
|
|
payload=payload, headers=_auth_headers(), link=_link())
|
|
|
|
def files_upload(self, filename: str, content_b64: str, by: str = "") -> dict[str, Any]:
|
|
with httpx.Client(timeout=max(settings.http_timeout, 60.0)) as client:
|
|
return link_crypto.call_json(
|
|
client, "POST", f"{self.base_url}/files/upload",
|
|
payload={"filename": filename, "content_b64": content_b64,
|
|
"by": by, "tenant": self.tenant},
|
|
headers=_auth_headers(), link=_link())
|
|
|
|
def files_status(self, path: str, status: str, by: str = "") -> dict[str, Any]:
|
|
"""Włączenie wgranego pliku do użytku. W demo wgranie i włączenie to JEDNA
|
|
czynność: „dodać do bazy" ma znaczyć, że plik od razu bierze udział
|
|
w wyszukiwaniu — inaczej demo wyglądałoby na zepsute."""
|
|
with httpx.Client(timeout=settings.http_timeout) as client:
|
|
return link_crypto.call_json(
|
|
client, "POST", f"{self.base_url}/files/status",
|
|
payload={"path": path, "status": status, "by": by, "tenant": self.tenant},
|
|
headers=_auth_headers(), link=_link())
|
|
|
|
def health(self) -> dict[str, Any]:
|
|
with httpx.Client(timeout=settings.http_timeout) as client:
|
|
r = client.get(f"{self.base_url}/health", headers=_auth_headers())
|
|
r.raise_for_status()
|
|
return r.json()
|