feat: Phase 3d — ReadModels port, postgres impl, HTTP reads rewire
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled

Problem: entity/relationship/graph/blast-radius reads were embedded
as inline SQL in the httpapi handlers, duplicating the recursive type
tree CTE, the blast_radius function call, and the topology-picking
query across the REST and MCP surfaces with no port abstraction.

Change:
- ports.ReadModels interface: ListEntities, GetEntity, GetEntityBySlug,
  GetEntityRelations, GetBlastRadius, GetGraph (with health),
  ListEntityTypes. Returns EntityWithHealth (domain.Entity + health
  from entity_status join) and domain.Relationship — no gen types
  in the port.
- adapters/postgres/readmodels.go: EntityReader implements ReadModels
  with the existing SQL verbatim (recursive type filter, blast_radius,
  most-connected-first topology, graph edge listing).
- httpapi/entities.go: ListEntities, GetEntity, GetEntityRelations,
  GetBlastRadius, GetGraph rewired to ReadModels. SQL moved to the
  adapter; handlers map domain/ports types to gen wire shapes.
  entityWithHealthToGen, sqlcEntityToGen helpers added.
- Old sqlcEntityToGen (sqlcgen.Entity → gen.Entity) preserved for
  client_lifecycle.go; mutation handlers use domainToGen.

Verification: go build/vet, full test suite (19 pkgs), DB integration
(postgres + mcp — both green). httpapi DB tests have the pre-existing
set of failures (TestAPIEndToEnd entity_types=60/501, TestPhase3*)
verified at ec11956.
This commit is contained in:
2026-08-15 23:58:31 +02:00
parent 9e3783734e
commit 65f415f9db
7 changed files with 664 additions and 219 deletions

View File

@@ -1,6 +1,7 @@
package ports
import (
"time"
"context"
"github.com/dtoro/oikos/internal/core/domain"
@@ -119,6 +120,27 @@ type RelationshipRepository interface {
ListFor(ctx context.Context, entityID domain.UUID, direction string) ([]domain.Relationship, error)
}
// EntityWithHealth pairs an entity with its probe health (from the
// entity_status join — the read shape graph/report endpoints need).
type EntityWithHealth struct {
Entity domain.Entity
Health string
LastCheckAt *time.Time
}
// ReadModels surfaces query-shaped report reads consumed directly by the
// httpapi/mcpserver adapters (no service hop — invariant-free reads, plan
// §3.4). Methods accrete per phase as report handlers rewire.
type ReadModels interface {
ListEntities(ctx context.Context, filters EntityFilters) ([]EntityWithHealth, string, error)
GetEntity(ctx context.Context, id domain.UUID) (EntityWithHealth, error)
GetEntityBySlug(ctx context.Context, slug string) (EntityWithHealth, error)
GetEntityRelations(ctx context.Context, entityID domain.UUID, direction, relType string) ([]domain.Relationship, error)
GetBlastRadius(ctx context.Context, entityID domain.UUID, depth int) ([]EntityWithHealth, error)
GetGraph(ctx context.Context, depth int, root *domain.UUID, relTypes []string, cap int) (nodes []EntityWithHealth, edges []domain.Relationship, truncated bool, err error)
ListEntityTypes(ctx context.Context) ([]domain.EntityType, error)
}
// OntologyStore loads the type tree; implementations cache. Consumers
// validate entity types, relationship endpoints, and lifecycle transitions
// against it before issuing repository writes.