Tech debt: unify policy metadata extraction with graph resolution
Tech debt in internal/policy, introduced by PR #711 and deferred by agreement. Resolving it also closes #714 and #715 (review finding A4).
PR #711 added ExtractPolicyMetadata + extractBundleMetadata + extractSplitMetadata, which duplicate the existing ResolvePolicyGraph + resolveBundleGraph + resolveSplitGraph nearly line-for-line: same shape detection, same layer load/parse, same error strings. Only the return type differs, and every field the metadata path computes already exists on DependencyGraph.
Proposal: build the graph, project the metadata off it, delete the duplicate helpers.
Why deferred, why it still matters
Deferred because it's a refactor that also has to settle two behavioral questions (#714, #715), and PR #711 is scoped to the feature.
Still matters because the two copies sit ~100 lines apart with no compiler link. Change the shape handling or the bundle keys in one and not the other, and the graph consumers (scan, doctor) and metadata consumers (list, get) silently disagree about the same policy. Cheapest to collapse now, while the copy is new and has no other callers.
The refactor
1. Add Title to DependencyGraph and set it in both resolve*Graph (one line each; policyLayerResult.Title already exists):
graph.EvaluatorID = policyLayer.EvaluatorID
graph.Title = policyLayer.Title // add
graph.Assessments = append(graph.Assessments, policyLayer.Assessments...)
2. Make ExtractPolicyMetadata a projection and delete the two extract* helpers:
func (r *Resolver) ExtractPolicyMetadata(policyID, version string) (PolicyMetadata, error) {
graph, err := r.ResolvePolicyGraph(policyID, version) // best-effort mode: see step 3
if err != nil {
return PolicyMetadata{}, err
}
meta := PolicyMetadata{
Title: graph.Title,
EvaluatorID: graph.EvaluatorID,
AssessmentCount: len(graph.Assessments),
}
if len(graph.Controls) > 0 && graph.Controls[0].Parsed != nil {
meta.ControlCount = len(graph.Controls[0].Parsed.Controls)
}
return meta, nil
}
3. Pick one error policy for the shared path. This is the real work: the graph path is stricter than the metadata path, so consolidating forces a decision.
| Condition |
scan / doctor |
Display |
Owner |
| Catalog present but invalid |
fail |
best-effort, ControlCount = 0 |
A2 |
| Catalog load fails |
distinguish absent vs I/O |
same |
#715 |
| No assessment-plans |
product decision |
show title/controls anyway |
#714 |
Suggested shape: a Strict vs BestEffort mode on the graph builder, so scan/doctor stay strict and display degrades gracefully, with the leniency living in one place.
Done when
References
Duplication at resolver.go:99-221 (PR head 4cad011d); reuse :223-357. Absent on base f61e943. From the PR #711 review (finding A4).
Related issues: #714, #715
Tech debt: unify policy metadata extraction with graph resolution
Tech debt in
internal/policy, introduced by PR #711 and deferred by agreement. Resolving it also closes #714 and #715 (review finding A4).PR #711 added
ExtractPolicyMetadata+extractBundleMetadata+extractSplitMetadata, which duplicate the existingResolvePolicyGraph+resolveBundleGraph+resolveSplitGraphnearly line-for-line: same shape detection, same layer load/parse, same error strings. Only the return type differs, and every field the metadata path computes already exists onDependencyGraph.Proposal: build the graph, project the metadata off it, delete the duplicate helpers.
Why deferred, why it still matters
Deferred because it's a refactor that also has to settle two behavioral questions (#714, #715), and PR #711 is scoped to the feature.
Still matters because the two copies sit ~100 lines apart with no compiler link. Change the shape handling or the bundle keys in one and not the other, and the graph consumers (
scan,doctor) and metadata consumers (list,get) silently disagree about the same policy. Cheapest to collapse now, while the copy is new and has no other callers.The refactor
1. Add
TitletoDependencyGraphand set it in bothresolve*Graph(one line each;policyLayerResult.Titlealready exists):2. Make
ExtractPolicyMetadataa projection and delete the twoextract*helpers:3. Pick one error policy for the shared path. This is the real work: the graph path is stricter than the metadata path, so consolidating forces a decision.
scan/doctorControlCount = 0Suggested shape: a
StrictvsBestEffortmode on the graph builder, soscan/doctorstay strict and display degrades gracefully, with the leniency living in one place.Done when
extract*Metadatahelpers are gone;ExtractPolicyMetadataprojects from the graph.adherence.assessment-plans#714 decision).listandgetnever disagree withscan.References
Duplication at
resolver.go:99-221(PR head4cad011d); reuse:223-357. Absent on basef61e943. From the PR #711 review (finding A4).Related issues: #714, #715