Problem: the hexagon's Phase 2 (plans/2026-08-15-hexagonal-architecture.md) must give the use-cases-to-be their contract surface: driven-port interfaces, test fakes, the secrets interface moved into core, and the postgres package inside the adapters tree — before the first vertical slice (Phase 3) can wire a composition root. Change: - internal/core/ports: full driven-port catalog per plan §3.3 — repositories as transaction-scoped aggregates whose inputs carry derived checks, audit, and events (§3.6), plus CommandExecutor, TargetResolver, Checker, Secrets, EventPublisher, Provisioner. Port-local payload types (Event, AuditEntry, CheckDef, KnowledgeEntry, ExecResult) keep signatures off infrastructure; TypeTree aliases internal/ontology (pure over domain) until checkdefaults is absorbed. ReadModels intentionally not declared yet — it materializes with the Phase 3 slice and grows as report handlers rewire. - secrets.Backend is now an alias of ports.Secrets; implementations (Infisical, SOPS, Manager) unchanged. mcp's local secretBackend subset is deleted; tool constructors take ports.Secrets. - internal/db → internal/adapters/postgres (mechanical import rewrite; package identifier stays db until the Phase 3 repository split). sqlc.yaml, Makefile, golangci exclusions, and docs follow the move; make generate-check verified. - internal/adapters/ssh: Executor implements ports.CommandExecutor over the actuator dial pool + RunStreaming (10-min default timeout carried over from the httpapi path). - internal/adapters/remote: Resolver implements ports.TargetResolver delegating to internal/remote (still pool-based; drops onto ports.EntityRepository when repositories land in Phase 3 — documented transitional import). - internal/core/ports/portstest: importable fakes — in-memory EntityRepo (with check-then-act SetState, side-effect recording), RecordingExecutor, FakeChecker, SpyPublisher; port-satisfaction guards; tests. Risk: ports are declared ahead of implementations — signatures firm up per phase as slices land (documented in the package doc); the remote→postgres transitional import is explicit and dissolves in Phase 3. Verification: go vet, make test (race, 19 packages), generate-check, golangci on core+adapters — 0 issues; full-repo baseline down 365→344.
83 lines
2.2 KiB
Go
83 lines
2.2 KiB
Go
package httpapi
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
|
|
"github.com/dtoro/oikos/internal/adapters/postgres/sqlcgen"
|
|
"github.com/dtoro/oikos/internal/httpapi/gen"
|
|
)
|
|
|
|
func (s *Server) GetOntology(ctx context.Context, req gen.GetOntologyRequestObject) (gen.GetOntologyResponseObject, error) {
|
|
resp := gen.GetOntology200JSONResponse{
|
|
EntityTypes: []gen.EntityType{},
|
|
RelationshipTypes: []gen.RelationshipType{},
|
|
Lifecycles: []gen.LifecycleDef{},
|
|
}
|
|
|
|
q := sqlcgen.New(s.pool)
|
|
|
|
etRows, err := q.ListEntityTypes(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, et := range etRows {
|
|
schemaVersion := int(et.SchemaVersion)
|
|
var schema *map[string]any
|
|
if len(et.AttributeSchema) > 0 {
|
|
var s map[string]any
|
|
if json.Unmarshal(et.AttributeSchema, &s) == nil && s != nil {
|
|
schema = &s
|
|
}
|
|
}
|
|
resp.EntityTypes = append(resp.EntityTypes, gen.EntityType{
|
|
Name: et.Name,
|
|
ParentType: et.ParentType,
|
|
IsAbstract: et.IsAbstract,
|
|
Domain: et.Domain,
|
|
Layer: gen.EntityTypeLayer(et.Layer),
|
|
Description: et.Description,
|
|
LifecycleId: et.LifecycleID,
|
|
SchemaVersion: &schemaVersion,
|
|
AttributeSchema: schema,
|
|
Status: gen.EntityTypeStatus(et.Status),
|
|
})
|
|
}
|
|
|
|
rtRows, err := q.ListRelationshipTypes(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, rt := range rtRows {
|
|
resp.RelationshipTypes = append(resp.RelationshipTypes, gen.RelationshipType{
|
|
Name: rt.Name,
|
|
Inverse: rt.Inverse,
|
|
SourceType: rt.SourceType,
|
|
TargetType: rt.TargetType,
|
|
Cardinality: gen.RelationshipTypeCardinality(rt.Cardinality),
|
|
Description: rt.Description,
|
|
})
|
|
}
|
|
|
|
lcRows, err := q.ListLifecycleDefs(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, lc := range lcRows {
|
|
terminal := lc.TerminalStates
|
|
var transitions map[string]any
|
|
if err := json.Unmarshal(lc.Transitions, &transitions); err != nil {
|
|
return nil, fmt.Errorf("lifecycle %s transitions: %w", lc.ID, err)
|
|
}
|
|
resp.Lifecycles = append(resp.Lifecycles, gen.LifecycleDef{
|
|
Id: lc.ID,
|
|
States: lc.States,
|
|
DefaultState: lc.DefaultState,
|
|
TerminalStates: &terminal,
|
|
Transitions: transitions,
|
|
})
|
|
}
|
|
|
|
return resp, nil
|
|
} |