初始化第一版

This commit is contained in:
andy committed 2026-09-05 15:46:37 +08:00
commit 8a6c31c14d
83 files changed
+14302

No files matched your search

+64
View File
@@ -0,0 +1,64 @@
// Command postgis-probe performs a read-only, non-record-level readiness audit.
package main
import (
"context"
"encoding/json"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"time"
"fire-safety-ymd/internal/config"
"fire-safety-ymd/internal/repository"
)
const probeTimeout = 45 * time.Second
func main() {
cfg, err := config.Load()
if err != nil {
log.Fatalf("load configuration: %v", err)
}
if !cfg.PostGIS.Enabled {
log.Fatalf("%s must be true to run the PostGIS readiness probe", config.PostGISEnabledEnv)
}
signalCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
ctx, cancel := context.WithTimeout(signalCtx, probeTimeout)
defer cancel()
postGIS, err := repository.OpenPostGIS(ctx, repository.PostGISOptions{
DSN: cfg.PostGIS.DSN,
MaxConns: cfg.PostGIS.MaxConns,
ConnectTimeout: cfg.PostGIS.ConnectTimeout,
QueryTimeout: cfg.PostGIS.QueryTimeout,
})
if err != nil {
log.Fatalf("initialize PostGIS: %v", err)
}
defer postGIS.Close()
var report repository.ReadinessReport
if cfg.PostGIS.ExpectedSRID == 0 {
report, err = postGIS.Audit(ctx)
} else {
report, err = postGIS.ValidateForMCP(ctx, cfg.PostGIS.ExpectedSRID)
}
if len(report.Tables) > 0 {
if encodeErr := json.NewEncoder(os.Stdout).Encode(report); encodeErr != nil {
log.Fatalf("encode readiness report: %v", encodeErr)
}
}
if err != nil {
log.Fatalf("audit PostGIS: %v", err)
}
if cfg.PostGIS.ExpectedSRID == 0 {
fmt.Fprintln(os.Stderr, "readiness note: FIRE_SAFETY_POSTGIS_EXPECTED_SRID is not set; audit only, MCP spatial validation was not performed")
return
}
fmt.Fprintln(os.Stderr, "MCP spatial validation passed")
}
+101
View File
@@ -0,0 +1,101 @@
// Command postgis-srid-migrate performs the explicitly approved EPSG:4326 metadata migration.
package main
import (
"context"
"encoding/json"
"flag"
"fmt"
"log"
"os"
"os/signal"
"strings"
"syscall"
"time"
"fire-safety-ymd/internal/migration"
"github.com/jackc/pgx/v5"
)
const (
migrationDSNEnv = "FIRE_SAFETY_POSTGIS_MIGRATION_DSN"
runtimeDSNEnv = "FIRE_SAFETY_POSTGIS_DSN"
commandTimeout = 3 * time.Minute
)
func main() {
apply := flag.Bool("apply", false, "commit the reviewed SRID metadata migration")
confirmation := flag.String("confirm-source-crs", "", "required with --apply; must equal EPSG:4326")
flag.Parse()
dsn, err := selectDSN(*apply)
if err != nil {
log.Fatal(err)
}
connectionConfig, err := pgx.ParseConfig(dsn)
if err != nil {
log.Fatal("parse PostGIS migration configuration")
}
connectionConfig.RuntimeParams["application_name"] = "fire-safety-ymd-srid-migration"
signalCtx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
ctx, cancel := context.WithTimeout(signalCtx, commandTimeout)
defer cancel()
conn, err := pgx.ConnectConfig(ctx, connectionConfig)
if err != nil {
log.Fatal("connect to PostGIS for SRID migration")
}
defer func() { _ = conn.Close(context.Background()) }()
if !*apply {
report, err := migration.Inspect(ctx, conn)
if err != nil {
log.Fatalf("inspect SRID migration state: %v", err)
}
writeJSON(report)
if err := migration.ValidateForApply(report); err != nil {
log.Fatalf("SRID migration preflight failed: %v", err)
}
fmt.Fprintln(os.Stderr, "preflight passed; no database changes were made")
return
}
if *confirmation != "EPSG:4326" {
log.Fatal("--apply requires --confirm-source-crs=EPSG:4326")
}
result, err := migration.ApplySRID4326(ctx, conn)
if err != nil {
log.Fatalf("apply SRID migration: %v", err)
}
writeJSON(result)
fmt.Fprintln(os.Stderr, "SRID metadata migration committed")
}
func selectDSN(apply bool) (string, error) {
migrationDSN := strings.TrimSpace(os.Getenv(migrationDSNEnv))
if apply {
if migrationDSN == "" {
return "", fmt.Errorf("%s is required with --apply", migrationDSNEnv)
}
return migrationDSN, nil
}
if migrationDSN != "" {
return migrationDSN, nil
}
runtimeDSN := strings.TrimSpace(os.Getenv(runtimeDSNEnv))
if runtimeDSN == "" {
return "", fmt.Errorf("%s or %s is required", migrationDSNEnv, runtimeDSNEnv)
}
return runtimeDSN, nil
}
func writeJSON(value any) {
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
if err := encoder.Encode(value); err != nil {
log.Fatalf("encode migration report: %v", err)
}
}
+31
View File
@@ -0,0 +1,31 @@
package main
import (
"context"
"log"
"os"
"os/signal"
"syscall"
"fire-safety-ymd/internal/app"
"fire-safety-ymd/internal/config"
)
func main() {
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
cfg, err := config.Load()
if err != nil {
log.Fatalf("load configuration: %v", err)
}
application, err := app.New(ctx, cfg)
if err != nil {
log.Fatalf("initialize application: %v", err)
}
log.Printf("HTTP server listening on %s", cfg.HTTPAddress)
if err := application.Run(ctx); err != nil {
log.Fatalf("run application: %v", err)
}
}
+118
View File
@@ -0,0 +1,118 @@
// Command superagent-probe performs a safe, explicit Open API connectivity check.
package main
import (
"context"
"crypto/rand"
"encoding/base64"
"encoding/json"
"fmt"
"log"
"os"
"os/signal"
"syscall"
"fire-safety-ymd/internal/config"
"fire-safety-ymd/internal/integration/superagent"
)
const probeMessage = "请只回复“SuperAgent connectivity OK”,不要调用业务工具。"
type probeResult struct {
Status string `json:"status"`
SessionID string `json:"session_id"`
RunID string `json:"run_id,omitempty"`
ProfileID string `json:"profile_id,omitempty"`
ProfileVersionID string `json:"profile_version_id,omitempty"`
ModelName string `json:"model_name,omitempty"`
Answer string `json:"answer"`
Usage superagent.TokenUsage `json:"usage"`
EventTypes []string `json:"event_types"`
}
func main() {
log.SetFlags(0)
cfg, err := config.Load()
if err != nil {
log.Fatalf("load configuration: %v", err)
}
if !cfg.SuperAgent.Enabled {
log.Fatalf("connectivity probe is disabled; set %s=true with a project-specific test credential", config.SuperAgentEnabledEnv)
}
client, err := superagent.NewHTTPClient(superagent.Config{
Enabled: cfg.SuperAgent.Enabled,
BaseURL: cfg.SuperAgent.BaseURL,
APIKey: cfg.SuperAgent.OpenAPIKey,
ConnectTimeout: cfg.SuperAgent.ConnectTimeout,
RecoveryMaxAttempts: cfg.SuperAgent.RecoveryMaxAttempts,
RecoveryInitialBackoff: cfg.SuperAgent.RecoveryInitialBackoff,
MaxMessageBytes: cfg.SuperAgent.MaxMessageBytes,
})
if err != nil {
log.Fatalf("create SuperAgent client: %v", err)
}
suffix, err := randomSuffix()
if err != nil {
log.Fatal("create probe correlation identifiers")
}
correlationID := "fsymd-probe-" + suffix
signalContext, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
defer stop()
ctx, cancel := context.WithTimeout(signalContext, cfg.SuperAgent.ProbeTimeout)
defer cancel()
session, err := client.CreateSession(ctx, superagent.CreateSessionRequest{
ExternalSubjectID: cfg.SuperAgent.ProbeSubjectID,
IdempotencyKey: correlationID + "-session",
RequestID: correlationID,
Metadata: map[string]any{
"source": "fire-safety-ymd",
"purpose": "connectivity-probe",
},
})
if err != nil {
log.Fatalf("create SuperAgent probe session: %v", err)
}
result, err := client.StreamMessage(ctx, superagent.StreamMessageRequest{
SessionID: session.ID,
Message: probeMessage,
IdempotencyKey: correlationID + "-message",
RequestID: correlationID,
Metadata: map[string]any{
"source": "fire-safety-ymd",
"purpose": "connectivity-probe",
},
}, nil)
if err != nil {
log.Fatalf("stream SuperAgent probe message: %v", err)
}
output := probeResult{
Status: "ok",
SessionID: result.SessionID,
RunID: result.RunID,
ProfileID: result.ProfileID,
ProfileVersionID: result.ProfileVersionID,
ModelName: result.ModelName,
Answer: result.Answer,
Usage: result.Usage,
EventTypes: result.EventTypes,
}
encoder := json.NewEncoder(os.Stdout)
encoder.SetIndent("", " ")
if err := encoder.Encode(output); err != nil {
log.Fatalf("write probe result: %v", err)
}
}
func randomSuffix() (string, error) {
value := make([]byte, 12)
if _, err := rand.Read(value); err != nil {
return "", fmt.Errorf("generate random suffix: %w", err)
}
return base64.RawURLEncoding.EncodeToString(value), nil
}