diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 5acc494..c00de1a 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -375,6 +375,13 @@ Focused check: `go test ./internal/engine -run 'Sequence|Collection|Inheritance| Finding: preparation portion of **D01**. Owners: `internal/engine/load.go`, `load_result.go`, `history.go`, `inheritance_load.go`. +Status: **complete** (preparation portion of D01). Prior completed commits: +`2736ddf`, `deb9aa5`, `ae3408a`, `ea27f17`, `08a8c25`, `877c497`, +`8aba43d`, `7bf2f0c`, `0f9ecf1`, `119b42c`, `247a12b`. Validation passed: +`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -run 'Load|History|Explain|Inheritance|Validation|Secret' -count=1`, +`env GOTOOLCHAIN=local GOPROXY=off GOTELEMETRY=off go test ./internal/engine -count=1`, +and `git diff --check`. + 1. Extract preparation of the existing `Inspection` document/history fields from publication. Produce the actual owned pre-CLI copy, inherited-operation copies, application lists, raw resolution snapshots, and coalesced replay that the result @@ -396,6 +403,12 @@ Acceptance: existing lifecycle and explanation behavior stays intact. Document the concrete retained document fields for Stage 13. This preparatory stage alone does not close D01; avoid temporary public state or compatibility APIs if the handoff is not yet buildable. + +Prepared retained roots for Stage 13 admission are `Inspection.baseline`, +`input`, `final`, `preCLI`, source/CLI application values, raw and coalesced +resolution before/after values, and inspection inheritance parent/effective +values. `Metadata.inheritance` holds its separate cloned parent/effective values; +the immutable document lookup is built only during publication. Focused check when buildable: `go test ./internal/engine -run 'Load|History|Explain|Inheritance|Validation|Secret'`. ## Stage 13 — Enforce The Quota On The Prepared Published Graph diff --git a/internal/engine/load.go b/internal/engine/load.go index 605a507..8834d38 100644 --- a/internal/engine/load.go +++ b/internal/engine/load.go @@ -341,8 +341,11 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo if hasErrors(diagnostics) { return nil, accumulator.loadError() } - historyApplications := append(cloneHistoryApplications(resolved.applications), cloneHistoryApplications(resolved.postDecodeApps)...) - if !historyNodesWithinLimit(decoded.baseline, decoded.final, resolved.final, historyApplications, resolved.snapshots, input.inheritance) { + inspection := prepareInspectionHistory(resolved) + historyApplications := make([]historyApplication, 0, len(inspection.applications)+len(inspection.postDecodeApps)) + historyApplications = append(historyApplications, inspection.applications...) + historyApplications = append(historyApplications, inspection.postDecodeApps...) + if !historyNodesWithinLimit(inspection.baseline, inspection.input, inspection.final, historyApplications, inspection.resolution, input.inheritance) { accumulator.add(Diagnostic{Code: CodeExplainLimitExceeded, Severity: SeverityError, Message: "retained configuration history exceeds the node limit"}) return nil, accumulator.loadError() } @@ -373,5 +376,5 @@ func executeLoadRequest[T any](request loadRequest[T], secrets SecretBinding, lo resolved.configuration = configuration // Publication detaches equal-valued validator replacements, preserving the // validated secret state, then finalizes metadata and sensitivity. - return publishLoadResult(schema, request.definition, resolved, secretResolution.references, &accumulator) + return publishLoadResult(schema, request.definition, resolved, inspection, historyApplications, secretResolution.references, &accumulator) } diff --git a/internal/engine/load_result.go b/internal/engine/load_result.go index 7183cd0..627ac57 100644 --- a/internal/engine/load_result.go +++ b/internal/engine/load_result.go @@ -72,7 +72,7 @@ func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T], } applications := []historyApplication(nil) if input.composition != nil { - applications = cloneHistoryApplications(input.composition.applications) + applications = input.composition.applications } else if input.document != nil { applications = []historyApplication{{layer: LayerBase, value: input.document}} } @@ -83,8 +83,8 @@ func resolveLoadInput[T any](schema *schemaDescriptor, definition Definition[T], 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, + 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 } @@ -99,7 +99,24 @@ func detachLoadConfiguration[T any](schema *schemaDescriptor, configuration T, m 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) { +func prepareInspectionHistory[T any](resolved resolvedLoad[T]) *Inspection { + return &Inspection{ + metadata: resolved.metadata, + baseline: resolved.decoded.baseline, + input: resolved.decoded.final, + inputOrigins: cloneOriginIndex(resolved.decoded.origins), + preCLI: resolved.preCLI, + preCLIOrigins: cloneOrigins(resolved.preCLIOrigins), + postDecodeApps: cloneHistoryApplications(resolved.postDecodeApps), + final: resolved.final, + resolution: resolved.snapshots, + resolutionReplay: coalescedResolutionSnapshots(resolved.snapshots), + applications: cloneHistoryApplications(resolved.applications), + inheritance: inheritanceOperations(resolved.inheritance), + } +} + +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) @@ -107,38 +124,23 @@ func publishLoadResult[T any](schema *schemaDescriptor, definition Definition[T] accumulator.add(*issue) return nil, accumulator.loadError() } - decoded, metadata := resolved.decoded, resolved.metadata - final, applications, resolutionSnapshots := resolved.final, resolved.applications, resolved.snapshots - 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) + 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(allApplications)+4) + retainedTrees := make([]*document.Node, 0, len(applications)+4) retainedTrees = append(retainedTrees, decoded.final, final, preCLI) - for _, application := range allApplications { + for _, application := range applications { 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), - 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), - } + 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 }