package engine import ( "reflect" "gitea.maximumdirect.net/eric/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 decoded decodedConfiguration metadata Metadata final *document.Node snapshots []resolutionSnapshot applications []historyApplication postDecodeApps []historyApplication structuralInput *document.Node preCLI *document.Node preCLIOrigins map[string]Origin referenceInput *document.Node versionEvidence []*sourceVersionEvidence inheritance *inheritanceScope 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 final := decoded.final referenceInput := prepared.referenceInput var resolutionSnapshots []resolutionSnapshot 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 applyResolutionMetadata(&metadata, resolved) referenceInput = documentWithMetadataLocations(final, metadata, input.document) } applications := []historyApplication(nil) if input.composition != nil { applications = cloneHistoryApplications(input.composition.applications) } else if input.document != nil { applications = []historyApplication{{layer: LayerBase, value: input.document}} } var versionEvidence []*sourceVersionEvidence if input.versionState != nil { versionEvidence = append(versionEvidence, input.versionState.evidence...) } return resolvedLoad[T]{ configuration: configuration, decoded: decoded, metadata: metadata, final: final, snapshots: resolutionSnapshots, applications: applications, postDecodeApps: cloneHistoryApplications(prepared.postDecodeApps), structuralInput: document.Clone(prepared.structuralInput), preCLI: document.Clone(prepared.preCLI), preCLIOrigins: cloneOrigins(prepared.preCLIOrigins), referenceInput: referenceInput, versionEvidence: versionEvidence, inheritance: input.inheritance, 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 publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T], resolved resolvedLoad[T], 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() } decoded, metadata := resolved.decoded, resolved.metadata final, applications, resolutionSnapshots := resolved.final, resolved.applications, resolved.snapshots structuralInput := document.Clone(resolved.structuralInput) preCLI := document.Clone(resolved.preCLI) allApplications := append(cloneHistoryApplications(applications), cloneHistoryApplications(resolved.postDecodeApps)...) finalizeMetadataPaths(&metadata, final, allApplications, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final) secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, final, allApplications, resolved.versionEvidence, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final) sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false) retainedTrees := make([]*document.Node, 0, len(allApplications)+5) retainedTrees = append(retainedTrees, decoded.final, final, structuralInput, preCLI) for _, application := range allApplications { retainedTrees = append(retainedTrees, application.value) } inspection := &Inspection{ metadata: metadata, sources: sources, contexts: cloneResourceContexts(resolved.contexts), diagnostics: canonicalDiagnostics(accumulator.diagnostics), baseline: decoded.baseline, input: decoded.final, inputOrigins: cloneOriginIndex(decoded.origins), structuralInput: structuralInput, preCLI: preCLI, preCLIOrigins: cloneOrigins(resolved.preCLIOrigins), postDecodeApps: cloneHistoryApplications(resolved.postDecodeApps), final: final, immutableLookup: buildDocumentLookup(retainedTrees...), resolution: resolutionSnapshots, resolutionReplay: coalescedResolutionSnapshots(resolutionSnapshots), applications: applications, inheritance: inheritanceOperations(resolved.inheritance), secrets: secrets, 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), } }