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
This commit is contained in:
sysops
2026-08-30 20:14:07 +02:00
parent e6892535ed
commit d6e9412043
4 changed files with 190 additions and 0 deletions
+70
View File
@@ -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)
}
}
@@ -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
+76
View File
@@ -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.
+30
View File
@@ -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=