generated from coulomb/repo-seed
208 lines
7.8 KiB
Go
208 lines
7.8 KiB
Go
package markitect
|
|
|
|
import (
|
|
"fmt"
|
|
"slices"
|
|
|
|
"github.com/netkingdom/flex-auth/internal/registry"
|
|
"github.com/netkingdom/flex-auth/pkg/api"
|
|
)
|
|
|
|
const (
|
|
// SystemID is the protected-system id emitted by Markitect manifests.
|
|
SystemID = "markitect-tool"
|
|
|
|
namespaceVersion = "markitect-resource-namespace-v1"
|
|
namespaceDoc = "docs/markitect-resource-namespace.md"
|
|
)
|
|
|
|
// Diagnostic describes a Markitect manifest import finding.
|
|
type Diagnostic struct {
|
|
Code string `json:"code"`
|
|
Severity string `json:"severity"`
|
|
Message string `json:"message"`
|
|
Resource string `json:"resource,omitempty"`
|
|
Fields []string `json:"fields,omitempty"`
|
|
}
|
|
|
|
// ImportResult captures the normalized manifest and diagnostics.
|
|
type ImportResult struct {
|
|
Manifest api.ResourceManifest `json:"manifest"`
|
|
Diagnostics []Diagnostic `json:"diagnostics,omitempty"`
|
|
}
|
|
|
|
type classification struct {
|
|
scopeLevel api.ScopeLevel
|
|
planes []api.Plane
|
|
parentTypes []string
|
|
}
|
|
|
|
var classifications = map[string]classification{
|
|
"knowledge_base": {
|
|
scopeLevel: api.ScopeLevelWorkspace,
|
|
planes: []api.Plane{api.PlaneIntent, api.PlaneData},
|
|
},
|
|
"repository": {
|
|
scopeLevel: api.ScopeLevelProject,
|
|
planes: []api.Plane{api.PlaneBuild, api.PlaneData},
|
|
parentTypes: []string{"knowledge_base"},
|
|
},
|
|
"document": {
|
|
scopeLevel: api.ScopeLevelResource,
|
|
planes: []api.Plane{api.PlaneData},
|
|
parentTypes: []string{"repository", "knowledge_base"},
|
|
},
|
|
"section": {
|
|
scopeLevel: api.ScopeLevelSubresource,
|
|
planes: []api.Plane{api.PlaneData},
|
|
parentTypes: []string{"document"},
|
|
},
|
|
"span": {
|
|
scopeLevel: api.ScopeLevelField,
|
|
planes: []api.Plane{api.PlaneData},
|
|
parentTypes: []string{"section", "document"},
|
|
},
|
|
"context_package": {
|
|
scopeLevel: api.ScopeLevelDataset,
|
|
planes: []api.Plane{api.PlaneIntent, api.PlaneData, api.PlanePolicy},
|
|
parentTypes: []string{"knowledge_base", "repository", "document"},
|
|
},
|
|
"workflow_artifact": {
|
|
scopeLevel: api.ScopeLevelProcess,
|
|
planes: []api.Plane{api.PlaneExecution, api.PlaneData, api.PlaneAudit},
|
|
parentTypes: []string{"context_package", "document"},
|
|
},
|
|
"export": {
|
|
scopeLevel: api.ScopeLevelRecord,
|
|
planes: []api.Plane{api.PlaneData, api.PlaneAudit},
|
|
parentTypes: []string{"workflow_artifact", "context_package", "document"},
|
|
},
|
|
}
|
|
|
|
// ImportResourceManifest validates, enriches, and imports a Markitect manifest.
|
|
func ImportResourceManifest(store *registry.Store, manifest api.ResourceManifest) (ImportResult, error) {
|
|
if store == nil {
|
|
return ImportResult{}, fmt.Errorf("registry store is required")
|
|
}
|
|
|
|
result := ImportResult{
|
|
Manifest: EnrichResourceManifest(manifest),
|
|
Diagnostics: ValidateResourceManifest(manifest),
|
|
}
|
|
if hasError(result.Diagnostics) {
|
|
return result, fmt.Errorf("markitect resource manifest has import errors")
|
|
}
|
|
if err := store.ImportResourceManifest(result.Manifest); err != nil {
|
|
return result, err
|
|
}
|
|
return result, nil
|
|
}
|
|
|
|
// ValidateResourceManifest returns Markitect-specific import diagnostics.
|
|
func ValidateResourceManifest(manifest api.ResourceManifest) []Diagnostic {
|
|
var diagnostics []Diagnostic
|
|
if manifest.ID == "" {
|
|
diagnostics = append(diagnostics, errorDiagnostic("MARKITECT-MANIFEST-ID", "", "manifest id is required", "id"))
|
|
}
|
|
if manifest.System != SystemID {
|
|
diagnostics = append(diagnostics, errorDiagnostic("MARKITECT-SYSTEM", "", fmt.Sprintf("manifest system must be %q", SystemID), "system"))
|
|
}
|
|
if manifest.Metadata["flex_auth_contract"] != api.FlexAuthContractV0 {
|
|
diagnostics = append(diagnostics, errorDiagnostic("MARKITECT-CONTRACT", "", "metadata.flex_auth_contract must declare resource-registration-v0", "metadata.flex_auth_contract"))
|
|
}
|
|
if manifest.CaringProfile == "" {
|
|
diagnostics = append(diagnostics, warningDiagnostic("MARKITECT-CARING-PROFILE", "", "missing caring_profile; importer will default to caring-0.4.0-rc2", "caring_profile"))
|
|
} else if manifest.CaringProfile != api.CaringProfileCaring040RC2 {
|
|
diagnostics = append(diagnostics, errorDiagnostic("MARKITECT-CARING-PROFILE", "", fmt.Sprintf("unsupported caring_profile %q", manifest.CaringProfile), "caring_profile"))
|
|
}
|
|
|
|
resourceTypes := make(map[string]string, len(manifest.Resources))
|
|
for _, resource := range manifest.Resources {
|
|
resourceTypes[resource.ID] = resource.Type
|
|
}
|
|
for _, resource := range manifest.Resources {
|
|
diagnostics = append(diagnostics, validateResource(resource, resourceTypes)...)
|
|
}
|
|
return diagnostics
|
|
}
|
|
|
|
// EnrichResourceManifest adds namespace and CARING classification metadata.
|
|
func EnrichResourceManifest(manifest api.ResourceManifest) api.ResourceManifest {
|
|
out := manifest
|
|
if out.CaringProfile == "" {
|
|
out.CaringProfile = api.CaringProfileCaring040RC2
|
|
}
|
|
out.Metadata = copyMap(out.Metadata)
|
|
out.Metadata["markitect_namespace"] = namespaceVersion
|
|
out.Metadata["markitect_namespace_doc"] = namespaceDoc
|
|
|
|
out.Resources = append([]api.Resource(nil), manifest.Resources...)
|
|
for i, resource := range out.Resources {
|
|
if class, ok := classifications[resource.Type]; ok {
|
|
resource.Attributes = copyMap(resource.Attributes)
|
|
resource.Attributes["markitect_resource_type"] = resource.Type
|
|
resource.Attributes["caring_scope_level"] = class.scopeLevel
|
|
resource.Attributes["caring_planes"] = append([]api.Plane(nil), class.planes...)
|
|
out.Resources[i] = resource
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func validateResource(resource api.Resource, resourceTypes map[string]string) []Diagnostic {
|
|
var diagnostics []Diagnostic
|
|
if resource.ID == "" {
|
|
diagnostics = append(diagnostics, errorDiagnostic("MARKITECT-RESOURCE-ID", "", "resource id is required", "resources[].id"))
|
|
}
|
|
if resource.Type == "" {
|
|
diagnostics = append(diagnostics, errorDiagnostic("MARKITECT-RESOURCE-TYPE", resource.ID, "resource type is required", "resources[].type"))
|
|
return diagnostics
|
|
}
|
|
|
|
class, ok := classifications[resource.Type]
|
|
if !ok {
|
|
diagnostics = append(diagnostics, errorDiagnostic("MARKITECT-RESOURCE-TYPE", resource.ID, fmt.Sprintf("unknown Markitect resource type %q", resource.Type), "resources[].type"))
|
|
return diagnostics
|
|
}
|
|
if resource.Parent != "" {
|
|
if parentType, ok := resourceTypes[resource.Parent]; ok && !slices.Contains(class.parentTypes, parentType) {
|
|
diagnostics = append(diagnostics, errorDiagnostic("MARKITECT-PARENT-TYPE", resource.ID, fmt.Sprintf("resource type %q cannot be parented by %q", resource.Type, parentType), "resources[].parent"))
|
|
}
|
|
}
|
|
if resource.Parent == "" && len(class.parentTypes) > 0 {
|
|
diagnostics = append(diagnostics, warningDiagnostic("MARKITECT-PARENT-MISSING", resource.ID, fmt.Sprintf("resource type %q usually declares a parent", resource.Type), "resources[].parent"))
|
|
}
|
|
if len(resource.Labels) == 0 {
|
|
diagnostics = append(diagnostics, warningDiagnostic("MARKITECT-LABELS-MISSING", resource.ID, "resource has no labels; CARING exposure classification may be ambiguous", "resources[].labels"))
|
|
}
|
|
if resource.TrustZone == "" {
|
|
diagnostics = append(diagnostics, warningDiagnostic("MARKITECT-TRUST-ZONE-MISSING", resource.ID, "resource has no trust_zone; CARING exposure classification may be ambiguous", "resources[].trust_zone"))
|
|
}
|
|
return diagnostics
|
|
}
|
|
|
|
func errorDiagnostic(code, resource, message string, fields ...string) Diagnostic {
|
|
return Diagnostic{Code: code, Severity: "error", Resource: resource, Message: message, Fields: fields}
|
|
}
|
|
|
|
func warningDiagnostic(code, resource, message string, fields ...string) Diagnostic {
|
|
return Diagnostic{Code: code, Severity: "warning", Resource: resource, Message: message, Fields: fields}
|
|
}
|
|
|
|
func hasError(diagnostics []Diagnostic) bool {
|
|
for _, diagnostic := range diagnostics {
|
|
if diagnostic.Severity == "error" {
|
|
return true
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func copyMap(in map[string]any) map[string]any {
|
|
out := make(map[string]any, len(in))
|
|
for key, value := range in {
|
|
out[key] = value
|
|
}
|
|
return out
|
|
}
|