Files
breakpilot-compliance/ai-compliance-sdk/internal/iace/store_components.go
Sharang Parnerkar a83056b5e7 refactor(go/iace): split hazard_library and store into focused files under 500 LOC
All oversized iace files now comply with the 500-line hard cap:
- hazard_library_ai_sw.go split into ai_sw (false_classification..communication)
  and ai_fw (unauthorized_access..update_failure)
- hazard_library_software_hmi.go split into software_hmi (software_fault+hmi)
  and config_integration (configuration_error+logging+integration)
- hazard_library_machine_safety.go split to keep mechanical/electrical/thermal/emc,
  safety_functions extracted into hazard_library_safety_functions.go
- store_hazards.go split: hazard library queries moved to store_hazard_library.go
- store_projects.go split: component and classification ops to store_components.go
- store_mitigations.go split: evidence/verification/ref-data to store_evidence.go
- hazard_library.go GetBuiltinHazardLibrary() updated to call all sub-functions
- All iace tests pass (go test ./internal/iace/...)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-19 09:35:02 +02:00

310 lines
8.9 KiB
Go

package iace
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"github.com/jackc/pgx/v5"
)
// ============================================================================
// Component CRUD Operations
// ============================================================================
// CreateComponent creates a new component within a project
func (s *Store) CreateComponent(ctx context.Context, req CreateComponentRequest) (*Component, error) {
comp := &Component{
ID: uuid.New(),
ProjectID: req.ProjectID,
ParentID: req.ParentID,
Name: req.Name,
ComponentType: req.ComponentType,
Version: req.Version,
Description: req.Description,
IsSafetyRelevant: req.IsSafetyRelevant,
IsNetworked: req.IsNetworked,
CreatedAt: time.Now().UTC(),
UpdatedAt: time.Now().UTC(),
}
_, err := s.pool.Exec(ctx, `
INSERT INTO iace_components (
id, project_id, parent_id, name, component_type,
version, description, is_safety_relevant, is_networked,
metadata, sort_order, created_at, updated_at
) VALUES (
$1, $2, $3, $4, $5,
$6, $7, $8, $9,
$10, $11, $12, $13
)
`,
comp.ID, comp.ProjectID, comp.ParentID, comp.Name, string(comp.ComponentType),
comp.Version, comp.Description, comp.IsSafetyRelevant, comp.IsNetworked,
comp.Metadata, comp.SortOrder, comp.CreatedAt, comp.UpdatedAt,
)
if err != nil {
return nil, fmt.Errorf("create component: %w", err)
}
return comp, nil
}
// GetComponent retrieves a component by ID
func (s *Store) GetComponent(ctx context.Context, id uuid.UUID) (*Component, error) {
var c Component
var compType string
var metadata []byte
err := s.pool.QueryRow(ctx, `
SELECT
id, project_id, parent_id, name, component_type,
version, description, is_safety_relevant, is_networked,
metadata, sort_order, created_at, updated_at
FROM iace_components WHERE id = $1
`, id).Scan(
&c.ID, &c.ProjectID, &c.ParentID, &c.Name, &compType,
&c.Version, &c.Description, &c.IsSafetyRelevant, &c.IsNetworked,
&metadata, &c.SortOrder, &c.CreatedAt, &c.UpdatedAt,
)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("get component: %w", err)
}
c.ComponentType = ComponentType(compType)
json.Unmarshal(metadata, &c.Metadata)
return &c, nil
}
// ListComponents lists all components for a project
func (s *Store) ListComponents(ctx context.Context, projectID uuid.UUID) ([]Component, error) {
rows, err := s.pool.Query(ctx, `
SELECT
id, project_id, parent_id, name, component_type,
version, description, is_safety_relevant, is_networked,
metadata, sort_order, created_at, updated_at
FROM iace_components WHERE project_id = $1
ORDER BY sort_order ASC, created_at ASC
`, projectID)
if err != nil {
return nil, fmt.Errorf("list components: %w", err)
}
defer rows.Close()
var components []Component
for rows.Next() {
var c Component
var compType string
var metadata []byte
err := rows.Scan(
&c.ID, &c.ProjectID, &c.ParentID, &c.Name, &compType,
&c.Version, &c.Description, &c.IsSafetyRelevant, &c.IsNetworked,
&metadata, &c.SortOrder, &c.CreatedAt, &c.UpdatedAt,
)
if err != nil {
return nil, fmt.Errorf("list components scan: %w", err)
}
c.ComponentType = ComponentType(compType)
json.Unmarshal(metadata, &c.Metadata)
components = append(components, c)
}
return components, nil
}
// UpdateComponent updates a component with a dynamic set of fields
func (s *Store) UpdateComponent(ctx context.Context, id uuid.UUID, updates map[string]interface{}) (*Component, error) {
if len(updates) == 0 {
return s.GetComponent(ctx, id)
}
query := "UPDATE iace_components SET updated_at = NOW()"
args := []interface{}{id}
argIdx := 2
for key, val := range updates {
switch key {
case "name", "version", "description":
query += fmt.Sprintf(", %s = $%d", key, argIdx)
args = append(args, val)
argIdx++
case "component_type":
query += fmt.Sprintf(", component_type = $%d", argIdx)
args = append(args, val)
argIdx++
case "is_safety_relevant":
query += fmt.Sprintf(", is_safety_relevant = $%d", argIdx)
args = append(args, val)
argIdx++
case "is_networked":
query += fmt.Sprintf(", is_networked = $%d", argIdx)
args = append(args, val)
argIdx++
case "sort_order":
query += fmt.Sprintf(", sort_order = $%d", argIdx)
args = append(args, val)
argIdx++
case "metadata":
metaJSON, _ := json.Marshal(val)
query += fmt.Sprintf(", metadata = $%d", argIdx)
args = append(args, metaJSON)
argIdx++
case "parent_id":
query += fmt.Sprintf(", parent_id = $%d", argIdx)
args = append(args, val)
argIdx++
}
}
query += " WHERE id = $1"
_, err := s.pool.Exec(ctx, query, args...)
if err != nil {
return nil, fmt.Errorf("update component: %w", err)
}
return s.GetComponent(ctx, id)
}
// DeleteComponent deletes a component by ID
func (s *Store) DeleteComponent(ctx context.Context, id uuid.UUID) error {
_, err := s.pool.Exec(ctx, "DELETE FROM iace_components WHERE id = $1", id)
if err != nil {
return fmt.Errorf("delete component: %w", err)
}
return nil
}
// ============================================================================
// Classification Operations
// ============================================================================
// UpsertClassification inserts or updates a regulatory classification for a project
func (s *Store) UpsertClassification(ctx context.Context, projectID uuid.UUID, regulation RegulationType, result string, riskLevel string, confidence float64, reasoning string, ragSources, requirements json.RawMessage) (*RegulatoryClassification, error) {
id := uuid.New()
now := time.Now().UTC()
_, err := s.pool.Exec(ctx, `
INSERT INTO iace_classifications (
id, project_id, regulation, classification_result,
risk_level, confidence, reasoning,
rag_sources, requirements,
created_at, updated_at
) VALUES (
$1, $2, $3, $4,
$5, $6, $7,
$8, $9,
$10, $11
)
ON CONFLICT (project_id, regulation)
DO UPDATE SET
classification_result = EXCLUDED.classification_result,
risk_level = EXCLUDED.risk_level,
confidence = EXCLUDED.confidence,
reasoning = EXCLUDED.reasoning,
rag_sources = EXCLUDED.rag_sources,
requirements = EXCLUDED.requirements,
updated_at = EXCLUDED.updated_at
`,
id, projectID, string(regulation), result,
riskLevel, confidence, reasoning,
ragSources, requirements,
now, now,
)
if err != nil {
return nil, fmt.Errorf("upsert classification: %w", err)
}
// Retrieve the upserted row (may have kept the original ID on conflict)
return s.getClassificationByProjectAndRegulation(ctx, projectID, regulation)
}
// getClassificationByProjectAndRegulation is a helper to fetch a single classification
func (s *Store) getClassificationByProjectAndRegulation(ctx context.Context, projectID uuid.UUID, regulation RegulationType) (*RegulatoryClassification, error) {
var c RegulatoryClassification
var reg, rl string
var ragSources, requirements []byte
err := s.pool.QueryRow(ctx, `
SELECT
id, project_id, regulation, classification_result,
risk_level, confidence, reasoning,
rag_sources, requirements,
created_at, updated_at
FROM iace_classifications
WHERE project_id = $1 AND regulation = $2
`, projectID, string(regulation)).Scan(
&c.ID, &c.ProjectID, &reg, &c.ClassificationResult,
&rl, &c.Confidence, &c.Reasoning,
&ragSources, &requirements,
&c.CreatedAt, &c.UpdatedAt,
)
if err == pgx.ErrNoRows {
return nil, nil
}
if err != nil {
return nil, fmt.Errorf("get classification: %w", err)
}
c.Regulation = RegulationType(reg)
c.RiskLevel = RiskLevel(rl)
json.Unmarshal(ragSources, &c.RAGSources)
json.Unmarshal(requirements, &c.Requirements)
return &c, nil
}
// GetClassifications retrieves all classifications for a project
func (s *Store) GetClassifications(ctx context.Context, projectID uuid.UUID) ([]RegulatoryClassification, error) {
rows, err := s.pool.Query(ctx, `
SELECT
id, project_id, regulation, classification_result,
risk_level, confidence, reasoning,
rag_sources, requirements,
created_at, updated_at
FROM iace_classifications
WHERE project_id = $1
ORDER BY regulation ASC
`, projectID)
if err != nil {
return nil, fmt.Errorf("get classifications: %w", err)
}
defer rows.Close()
var classifications []RegulatoryClassification
for rows.Next() {
var c RegulatoryClassification
var reg, rl string
var ragSources, requirements []byte
err := rows.Scan(
&c.ID, &c.ProjectID, &reg, &c.ClassificationResult,
&rl, &c.Confidence, &c.Reasoning,
&ragSources, &requirements,
&c.CreatedAt, &c.UpdatedAt,
)
if err != nil {
return nil, fmt.Errorf("get classifications scan: %w", err)
}
c.Regulation = RegulationType(reg)
c.RiskLevel = RiskLevel(rl)
json.Unmarshal(ragSources, &c.RAGSources)
json.Unmarshal(requirements, &c.Requirements)
classifications = append(classifications, c)
}
return classifications, nil
}