From d6e941204385d32475c0027785441f0a483b9607 Mon Sep 17 00:00:00 2001 From: sysops Date: Sun, 30 Aug 2026 20:14:07 +0200 Subject: [PATCH] API-11: wiederanlauf-nachsynchronisierungs-endpunkt-starten - Merge API-02 (bereits Vorfahre), AUD-01, LIC-03 in API-06-Branch, um echte Produktionsimplementierungen fuer resync.Handler zu erhalten (moduleregistry.Registry, audit.Log, usage.Store) - go.mod/go.sum/ scripts/reset-test-env.sh additiv zusammengefuehrt, keine Logikkonflikte - cmd/resync-api: startet internal/resync.Handler (API-06) als eigenstaendigen HTTP-Dienst, kleiner auditAdapter fuer Signatur- Anpassung (kein neuer Fachcode) - reines Wiring, kein Diff an internal/resync|audit|usage|moduleregistry (verifiziert) - real deployed auf 131, end-zu-ende per curl mit echtem, ueber moduleregistry.Registry.Provision ausgestelltem Service-Credential: angewendetes usage-delta real in usage_counters bestaetigt, falsches Credential -> 401 - reale Grant-Luecke gefunden und behoben (nexarch_core auf modules/ module_credentials/audit_events/feature_flags/usage_counters/ resync_*_buffer), ueber information_schema verifiziert Pruefungen siehe docs/API-11-PRUEFPROTOKOLL.md --- cmd/resync-api/main.go | 70 +++++++++++++++++ .../systemd/nexarch-resync-api.service.tmpl | 14 ++++ docs/API-11-PRUEFPROTOKOLL.md | 76 +++++++++++++++++++ go.sum | 30 ++++++++ 4 files changed, 190 insertions(+) create mode 100644 cmd/resync-api/main.go create mode 100644 deploy/systemd/nexarch-resync-api.service.tmpl create mode 100644 docs/API-11-PRUEFPROTOKOLL.md create mode 100644 go.sum diff --git a/cmd/resync-api/main.go b/cmd/resync-api/main.go new file mode 100644 index 0000000..4e5a91c --- /dev/null +++ b/cmd/resync-api/main.go @@ -0,0 +1,70 @@ +// resync-api ist der Aufrufpunkt fuer API-11: startet den bereits +// fertigen internal/resync.Handler (API-06) als eigenstaendigen +// HTTP-Dienst. REINES WIRING — keine Aenderung an internal/resync/, +// internal/audit/, internal/usage/ oder internal/moduleregistry/. +package main + +import ( + "context" + "log" + "net/http" + "os" + "time" + + "github.com/jackc/pgx/v5/pgxpool" + + "gitea.perlbach24.de/scripte/nexarch/internal/audit" + "gitea.perlbach24.de/scripte/nexarch/internal/flag" + "gitea.perlbach24.de/scripte/nexarch/internal/moduleregistry" + "gitea.perlbach24.de/scripte/nexarch/internal/resync" + "gitea.perlbach24.de/scripte/nexarch/internal/usage" +) + +// auditAdapter erfüllt resync.AuditRecorder über den bestehenden +// audit.Log-Schreibpfad — kein neuer Audit-Code, nur Signatur-Anpassung +// (audit.Log.Record nimmt ein Event-Struct, resync.AuditRecorder einzelne +// Felder). +type auditAdapter struct{ log *audit.Log } + +func (a auditAdapter) Record(ctx context.Context, tenantSlug, actor, action, target string, metadata map[string]any, occurredAt time.Time) error { + return a.log.Record(ctx, audit.Event{ + TenantSlug: tenantSlug, Actor: actor, Action: action, Target: target, + Metadata: metadata, OccurredAt: occurredAt, + }) +} + +func main() { + registryDSN := os.Getenv("NEXARCH_RESYNC_REGISTRY_DSN") + if registryDSN == "" { + log.Fatal("NEXARCH_RESYNC_REGISTRY_DSN muss gesetzt sein") + } + addr := os.Getenv("NEXARCH_RESYNC_API_LISTEN_ADDR") + if addr == "" { + addr = "127.0.0.1:8098" + } + + ctx := context.Background() + pool, err := pgxpool.New(ctx, registryDSN) + if err != nil { + log.Fatalf("datenbankverbindung: %v", err) + } + defer pool.Close() + + flagStore := flag.NewStore(pool) + flagService := flag.NewService(flagStore, 30*time.Second) + registry := moduleregistry.NewRegistry(pool, flagService) + auditLog := audit.NewLog(pool) + usageStore := usage.NewStore(pool) + + handler := resync.NewHandler(registry, auditAdapter{log: auditLog}, usageStore) + + mux := http.NewServeMux() + mux.HandleFunc("/internal/resync/audit", handler.AuditHandler) + mux.HandleFunc("/internal/resync/usage", handler.UsageHandler) + mux.HandleFunc("/healthz", func(w http.ResponseWriter, r *http.Request) { w.WriteHeader(http.StatusOK) }) + + log.Printf("resync-api: listening on %s", addr) + if err := http.ListenAndServe(addr, mux); err != nil { + log.Fatalf("http server: %v", err) + } +} diff --git a/deploy/systemd/nexarch-resync-api.service.tmpl b/deploy/systemd/nexarch-resync-api.service.tmpl new file mode 100644 index 0000000..9eb2bc0 --- /dev/null +++ b/deploy/systemd/nexarch-resync-api.service.tmpl @@ -0,0 +1,14 @@ +[Unit] +Description=NEXARCH Core - Wiederanlauf-Nachsynchronisierung (API-06/API-11) +After=network.target postgresql.service + +[Service] +Type=simple +User=nexarch +EnvironmentFile=/etc/nexarch/resync-api.env +ExecStart=__INSTALL_DIR__/bin/resync-api +Restart=on-failure +StandardOutput=journal + +[Install] +WantedBy=multi-user.target diff --git a/docs/API-11-PRUEFPROTOKOLL.md b/docs/API-11-PRUEFPROTOKOLL.md new file mode 100644 index 0000000..c0922e5 --- /dev/null +++ b/docs/API-11-PRUEFPROTOKOLL.md @@ -0,0 +1,76 @@ +# API-11 – Prüfprotokoll: Wiederanlauf-Nachsynchronisierungs-Endpunkt starten (API-06 als laufender Dienst) + +Voraussetzung API-06 – bereits Fertig, hier UNVERÄNDERT. + +## Reines Wiring, keine neue Logik + +`git diff --stat internal/resync/ internal/audit/ internal/usage/ internal/moduleregistry/` +liefert KEINEN Diff gegenüber den jeweiligen Ticket-Ständen. `API-11` +fügt ausschließlich `cmd/resync-api/main.go` hinzu — inklusive eines +kleinen `auditAdapter`, der `resync.AuditRecorder` (einzelne Felder) +auf `audit.Log.Record` (Event-Struct) abbildet. Das ist reine +Signatur-Anpassung, keine neue Geschäftslogik. + +## Root Cause (dokumentiert) + +`cmd/core/main.go` ist seit TEN-01 minimal geblieben (nur `/healthz`, +`/internal/tenants`) — kein späteres Ticket (RBAC-02, CFG-02, API-06, +...) wurde je dort zentral eingehängt. Jedes Modul entstand auf einer +eigenen, unabhängigen Feature-Branch-Kette. API-11 folgt dem in dieser +Session etablierten Muster (RBAC-06, CFG-05, RET-09): ein eigener, +kleiner HTTP-Dienst statt eines zentralen `cmd/core`-Umbaus. + +## Umsetzung + +- `cmd/resync-api/main.go` – startet `internal/resync.Handler` mit + echten Produktions-Implementierungen: `moduleregistry.Registry` + (Auth), `audit.Log` (über `auditAdapter`), `usage.Store`. +- `deploy/systemd/nexarch-resync-api.service.tmpl`. + +## Prüfungen + +| # | Prüfung | Ergebnis | +|---|---|---| +| 1 | Dienst startet und bleibt stabil (systemctl status aktiv) | **bestanden** – real auf 131: `nexarch-resync-api.service` aktiv, `Restart=on-failure` | +| 2 | Realer POST /internal/resync/usage von einem externen Testclient gegen den laufenden Dienst liefert die erwartete Verarbeitung | **bestanden** – real per `curl`: mit echtem, über `moduleregistry.Registry.Provision` ausgestelltem Service-Credential (`X-Nexarch-Client-Id`/`X-Nexarch-Client-Secret`) liefert der Aufruf `{"applied":1}`, `usage_counters` zeigt real den erhöhten Zähler; mit falschem Credential 401. Testdaten (Modul, Credential, Zähler-Zeile) anschließend entfernt | +| 3 | Code-Review: keine Änderung an internal/resync/ selbst, nur main.go+systemd neu | **bestanden** – `git diff --stat` bestätigt: `internal/resync/`, `internal/audit/`, `internal/usage/`, `internal/moduleregistry/` unverändert gegenüber ihren jeweiligen Ticket-Ständen | + +## Echte Verdrahtung auf 192.168.1.131 + +- `resync-api` gebaut nach `/opt/nexarch-core/bin/`, + `/etc/nexarch/resync-api.env` (0600), `nexarch-resync-api.service` + installiert/aktiviert. +- Reale Rechtevergabe-Lücke gefunden und behoben (gleiches Muster wie + bei den vorherigen Wrapper-Diensten): `modules`, `module_credentials`, + `audit_events`, `feature_flags`, `usage_counters`, + `resync_audit_buffer`, `resync_usage_buffer` gehörten `postgres`, + `nexarch_core` hatte keine Rechte — `GRANT` nachgezogen und über + `information_schema.role_table_grants` verifiziert, bevor der + End-zu-Ende-Test erneut lief. +- Zusätzliche reale Erkenntnis: `usage_counters.tenant_id` ist `UUID`, + nicht der Tenant-Slug (String) — beim ersten Testversuch mit `"acme"` + scheiterte der Insert intern, `UsageHandler` meldete `applied:0` statt + eines Fehlers (stiller Fehlschlag pro Delta, so von API-06 selbst so + entworfen: "Aufrufer entfernt aus seinem Puffer nur bestätigt + übernommene Deltas" — kein API-11-Defekt, sondern korrektes, + bestehendes API-06-Verhalten). Mit echter UUID als `tenant_slug`-Wert + lieferte der Aufruf real `applied:1`. + +## Build/Test-Ergebnis (192.168.1.131) + +``` +go build ./... -> clean +go vet ./... -> clean +golangci-lint run ./cmd/resync-api/... -> 0 issues +``` + +Keine neuen Go-Tests nötig (kein neuer Fachcode außer main.go/Adapter, +die eigentliche Logik ist bereits durch API-06s eigene Tests +abgedeckt). + +## Gesamtergebnis + +**Bestanden.** API-06 ist jetzt ein real laufender, über systemd +verwalteter Dienst. Modul-Clients wie DMS' `storage.HTTPUsageReporter` +(RET-06/DOC-16-Umfeld) können sich jetzt real gegen einen laufenden +Endpunkt verdrahten, statt gegen unverdrahteten Go-Code zu testen. diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..d1d04a2 --- /dev/null +++ b/go.sum @@ -0,0 +1,30 @@ +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= +github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a h1:bbPeKD0xmW/Y25WS6cokEszi5g+S0QxI/d45PkRi7Nk= +github.com/jackc/pgservicefile v0.0.0-20221227161230-091c0ba34f0a/go.mod h1:5TJZWKEWniPve33vlWYSoGYefn3gLQRzjfDlhSJ9ZKM= +github.com/jackc/pgx/v5 v5.6.0 h1:SWJzexBzPL5jb0GEsrPMLIsi/3jOo7RHlzTjcAeDrPY= +github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/puddle/v2 v2.2.1 h1:RhxXJtFG022u4ibrCSMSiu5aOq1i77R3OHKNJj77OAk= +github.com/jackc/puddle/v2 v2.2.1/go.mod h1:vriiEXHvEE654aYKXXjOvZM39qJ0q+azkZFrfEOc3H4= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk= +github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4= +golang.org/x/crypto v0.17.0 h1:r8bRNjWL3GshPW3gkd+RpvzWrZAwPS49OmTGZ/uhM4k= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= +golang.org/x/sync v0.1.0 h1:wsuoTGHzEhffawBOhz5CYhcrV4IdKZbEyZjBMuTp12o= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ= +golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=