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 = 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: prepared.postDecodeApps, structuralInput: document.Clone(prepared.structuralInput), preCLI: prepared.preCLI, preCLIOrigins: 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 prepareInspectionHistory[T any](resolved resolvedLoad[T], counter *retainedNodeCounter) (*Inspection, bool) { metadata := resolved.metadata metadata.inheritance = nil inspection := &Inspection{ metadata: metadata, baseline: resolved.decoded.baseline, input: resolved.decoded.final, inputOrigins: cloneOriginIndex(resolved.decoded.origins), preCLI: resolved.preCLI, preCLIOrigins: cloneOrigins(resolved.preCLIOrigins), final: resolved.final, resolution: resolved.snapshots, resolutionReplay: coalescedResolutionSnapshots(resolved.snapshots), } if !addInspectionHistory(counter, inspection) { return nil, false } var ok bool inspection.applications, ok = cloneHistoryApplicationsWithinLimit(resolved.applications, counter) if !ok { return nil, false } inspection.postDecodeApps, ok = cloneHistoryApplicationsWithinLimit(resolved.postDecodeApps, counter) if !ok { return nil, false } if resolved.inheritance != nil { roots := inheritanceOperationDocumentRoots(resolved.inheritance.operations) if !counter.canAddClones(roots, 2) { return nil, false } inspection.metadata.inheritance = cloneInheritanceOperations(resolved.inheritance.operations) if !addInheritanceOperationDocuments(counter, inspection.metadata.inheritance) { return nil, false } inspection.inheritance = cloneInheritanceOperations(resolved.inheritance.operations) if !addInheritanceOperationDocuments(counter, inspection.inheritance) { return nil, false } } if !addInspectionHistory(counter, inspection) { return nil, false } return inspection, true } func cloneHistoryApplicationsWithinLimit(values []historyApplication, counter *retainedNodeCounter) ([]historyApplication, bool) { for _, value := range values { if !counter.add(value.value) { return nil, false } } return cloneHistoryApplications(values), true } func inheritanceOperationDocumentRoots(operations []inheritanceOperation) []*document.Node { roots := make([]*document.Node, 0, len(operations)*2) for _, operation := range operations { roots = append(roots, operation.parent, operation.effective) } return roots } func addInheritanceOperationDocuments(counter *retainedNodeCounter, operations []inheritanceOperation) bool { for _, operation := range operations { if !counter.add(operation.parent) || !counter.add(operation.effective) { return false } } return true } func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T], resolved resolvedLoad[T], inspection *Inspection, applications []historyApplication, 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 := resolved.decoded metadata, final, preCLI := inspection.metadata, inspection.final, inspection.preCLI finalizeMetadataPaths(&metadata, final, applications, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final) secrets := classifySecretPathsWithEvidence(schema, definition.Secret, metadata, final, applications, resolved.versionEvidence, decoded.baseline, resolved.structuralInput, resolved.preCLI, decoded.final) sources := sourceInfosWithVersionEvidence(accumulator.sources, resolved.versionEvidence, secrets, false) retainedTrees := make([]*document.Node, 0, len(applications)+4) retainedTrees = append(retainedTrees, decoded.final, final, preCLI) for _, application := range applications { retainedTrees = append(retainedTrees, application.value) } inspection.metadata = metadata inspection.sources = sources inspection.contexts = cloneResourceContexts(resolved.contexts) inspection.diagnostics = canonicalDiagnostics(accumulator.diagnostics) inspection.immutableLookup = buildDocumentLookup(retainedTrees...) 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), } }