feat: Phase 3d — ReadModels port, postgres impl, HTTP reads rewire
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:
@@ -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.
|
||||
|
||||
@@ -117,6 +117,9 @@ func (r *EntityRepo) Create(_ context.Context, in ports.EntityCreateInput) (doma
|
||||
if _, dup := r.bySlug[in.Entity.Slug]; dup {
|
||||
return domain.Entity{}, domain.ErrConflict
|
||||
}
|
||||
if in.Entity.Version == 0 {
|
||||
in.Entity.Version = 1
|
||||
}
|
||||
r.store(in.Entity)
|
||||
r.Audits = append(r.Audits, in.Audit...)
|
||||
if in.Event != nil {
|
||||
@@ -151,6 +154,9 @@ func (r *EntityRepo) Update(_ context.Context, in ports.EntityUpdateInput) (doma
|
||||
}
|
||||
after := in.Entity
|
||||
after.Version = current.Version + 1
|
||||
if in.RederiveChecks {
|
||||
r.Rederived = append(r.Rederived, after.ID)
|
||||
}
|
||||
delete(r.bySlug, current.Slug)
|
||||
r.store(after)
|
||||
r.Audits = append(r.Audits, in.Audit...)
|
||||
|
||||
Reference in New Issue
Block a user