package engine import ( "reflect" "go.maximumdirect.net/configkit/internal/document" ) // resolvedLoad carries typed phase outputs needed for validation and publication; // it owns no source access, options, or long-lived schema state. type resolvedLoad[T any] struct { configuration T metadata Metadata final *document.Node referenceInput *document.Node versionEvidence []*sourceVersionEvidence contexts map[string]ResourceContext } func decodeLoadInput[T any](schema *schemaDescriptor, definition Definition[T], input assembledInput) (decodedConfiguration, []Diagnostic) { defaults := reflect.Zero(reflect.TypeFor[T]()) if definition.Defaults != nil { defaults = reflect.ValueOf(definition.Defaults()) } context := decodeContext{} if definition.Version != nil { context.versionPath = &definition.Version.Path } decoded, decodeIssues := decodeConfigurationWithContext(schema, defaults, input.document, context) if len(decodeIssues) != 0 { diagnostics := make([]Diagnostic, len(decodeIssues)) for i, issue := range decodeIssues { diagnostics[i] = diagnosticFromDecodeIssue(issue) } return decodedConfiguration{}, diagnostics } if input.inheritance != nil { applyCompositionOrigins(&decoded, input.inheritance.origins) } else if input.composition != nil { applyCompositionOrigins(&decoded, input.composition.origins) } return decoded, nil } func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T], input assembledInput, prepared postDecodeOverrideContext) (resolvedLoad[T], []Diagnostic) { var diagnostics []Diagnostic decoded := prepared.decoded configuration := decoded.value.Interface().(T) metadata := prepared.metadata builder := metadata.historyBuilder final := decoded.final referenceInput := prepared.referenceInput var resolutionSnapshots []resolutionSnapshot refreshReferenceInput := false if definition.Resolve != nil { resolved := runResolution(&configuration, definition.Resolve, schema, decoded, referenceInput, prepared.resolution) diagnostics = append(diagnostics, resolved.diagnostics...) if hasErrors(resolved.diagnostics) { return resolvedLoad[T]{}, diagnostics } final = resolved.final resolutionSnapshots = resolved.snapshots if builder != nil && len(resolutionSnapshots) != 0 { builder.observeResolutionEvidence(resolutionSnapshots) } applyResolutionMetadata(&metadata, resolved, prepared.resolution) finalizeEffectiveMetadataPaths(&metadata, final) refreshReferenceInput = true } if builder != nil { builder.model.final = final } if refreshReferenceInput { referenceInput = documentWithMetadataLocations(final, metadata, input.document) } var versionEvidence []*sourceVersionEvidence if input.versionState != nil { versionEvidence = append(versionEvidence, input.versionState.evidence...) } return resolvedLoad[T]{ configuration: configuration, metadata: metadata, final: final, referenceInput: referenceInput, versionEvidence: versionEvidence, contexts: input.contexts, }, diagnostics } func detachLoadConfiguration[T any](schema *schemaDescriptor, configuration T, mode secretCloneMode) (T, *Diagnostic) { detached, issue := cloneConfigurationWithMode(schema, reflect.ValueOf(configuration), defaultProjectionLimits(), mode) if issue != nil { diagnostic := diagnosticFromDecodeIssue(decodeIssueFromProjection(issue)) var zero T return zero, &diagnostic } return detached.Interface().(T), nil } func prepareInspectionHistory[T any](resolved resolvedLoad[T], counter *retainedNodeCounter) (*Inspection, bool) { metadata := resolved.metadata freezeMetadataHistory(&metadata, resolved.final) model := metadata.historyModel inspection := &Inspection{ metadata: metadata, history: model, final: resolved.final, } if !addInspectionHistory(counter, inspection) { return nil, false } return inspection, true } func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T], resolved resolvedLoad[T], inspection *Inspection, references map[string]string, accumulator *diagnosticAccumulator) (*Loaded[T], error) { // A read-only validator can replace a value with an equal borrowed object. // Detach without re-reading the environment or discarding validated state. configuration, issue := detachLoadConfiguration(schema, resolved.configuration, cloneValidatedSecrets) if issue != nil { accumulator.add(*issue) return nil, accumulator.loadError() } metadata := inspection.metadata secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, resolved.versionEvidence) sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false) inspection.metadata = metadata inspection.sources = sources inspection.contexts = cloneResourceContexts(resolved.contexts) inspection.diagnostics = canonicalDiagnostics(accumulator.diagnostics) inspection.secrets = secrets inspection.secretReferences = cloneSecretReferences(references) return &Loaded[T]{Config: configuration, Inspection: inspection}, nil } func diagnosticFromSchemaIssue(issue *schemaIssue) Diagnostic { return Diagnostic{Code: issue.code, Severity: SeverityError, Path: issue.path.clone(), Message: issue.message} } func diagnosticFromDecodeIssue(issue decodeIssue) Diagnostic { return Diagnostic{ Code: issue.code, Severity: SeverityError, Path: issue.path.clone(), Message: issue.message, Primary: documentLocationPointer(issue.location), } }