feat: Phase 4 — RelationshipService, postgres RelRepo, converged edges
Some checks failed
ci / build-test (push) Has been cancelled
ci / docker-build (push) Has been cancelled

Problem: relationship create/end existed as three drifted copies
(HTTP CreateRelationship/EndRelationship, MCP create_relationship/
end_relationship) with inline SQL, no ontology edge validation on
either path, and no audit on the MCP path.

Change:
- Internal/adapters/postgres/repositories.go: RelRepo implements
  ports.RelationshipRepository (Create/End/ListFor) over the pool,
  with in-tx upsert + audit/event side effects on Create.
- Internal/core/app/relationships.go: RelationshipService validates
  edges against the cached ontology TypeTree (tree.ValidateEdge) and
  delegates the tx to the repository. The adapter resolves slug→entity
  and extracts types before calling the service.
- HTTP CreateRelationship: resolves source/target via ReadModels,
  passes resolved types to RelationshipService for edge validation.
  EndRelationship calls the service directly (audit stays in the
  adapter for End — a simple toggle with no ontology check).
- MCP create_relationship/end_relationship: rewired to the service
  (pool resolves entity IDs inline for the tool handlers; the service
  validates edges and writes audit). The MCP path now gets ontology
  validation and audit coverage for the first time.
- Composition root: RelationshipService built with RelRepo + Ontology
  and wired through httpapi.NewHandler, ListenAndServe, and MCP
  constructors.

Verification: go build/vet, full test suite (19 pkgs, DB integration
postgres+mcp green).
This commit is contained in:
2026-08-16 00:09:40 +02:00
parent 973a6bd92a
commit b02f94bfd4
13 changed files with 271 additions and 112 deletions

View File

@@ -113,11 +113,13 @@ func main() {
// Build composition-root dependencies (ADR 0016).
entityRepo := db.NewEntityRepo(pool)
onto := db.NewOntologyRepo(pool, time.Minute)
readModels := db.NewEntityReader(pool)
entities := app.NewEntityService(entityRepo, db.NewOntologyRepo(pool, time.Minute))
entities := app.NewEntityService(entityRepo, onto)
relService := app.NewRelationshipService(db.NewRelRepo(pool), onto)
slog.Info("all: starting api with scheduler + execution-worker in background")
if err := httpapi.ListenAndServe(ctx, pool, cfg, entities, entityRepo, readModels); err != nil {
if err := httpapi.ListenAndServe(ctx, pool, cfg, entities, entityRepo, readModels, relService); err != nil {
slog.Error("api failed", "error", err)
os.Exit(1)
}
@@ -297,10 +299,12 @@ func runAPI(ctx context.Context, cfg config.Config) error {
// Composition root — build the service dependencies (ADR 0016, plan §3.5).
entityRepo := db.NewEntityRepo(pool)
onto := db.NewOntologyRepo(pool, time.Minute)
readModels := db.NewEntityReader(pool)
entities := app.NewEntityService(entityRepo, db.NewOntologyRepo(pool, time.Minute))
entities := app.NewEntityService(entityRepo, onto)
relService := app.NewRelationshipService(db.NewRelRepo(pool), onto)
err = httpapi.ListenAndServe(ctx, pool, cfg, entities, entityRepo, readModels)
err = httpapi.ListenAndServe(ctx, pool, cfg, entities, entityRepo, readModels, relService)
if err == http.ErrServerClosed {
return nil
}