Add evidence context policy preparation

This commit is contained in:
2026-07-27 18:04:12 +00:00
parent 64d461fc18
commit 224a8292c4
21 changed files with 900 additions and 98 deletions

View File

@@ -213,15 +213,16 @@ func (resolved ResolvedPipeline) AllArtifactLanes() []ResolvedArtifactLane {
}
type ModuleCatalog struct {
Inputs *InputAdapterRegistry
Chunkers *ChunkerRegistry
ArtifactCodecs *ArtifactCodecRegistry
Extractors *ExtractorRegistry
Mergers *MergerRegistry
Normalizers *NormalizerRegistry
Validators *ValidatorRegistry
ValidatorChains *ValidatorChainRegistry
Outputs *OutputEncoderRegistry
Inputs *InputAdapterRegistry
Chunkers *ChunkerRegistry
ArtifactCodecs *ArtifactCodecRegistry
ArtifactEvidence *ArtifactEvidenceRegistry
Extractors *ExtractorRegistry
Mergers *MergerRegistry
Normalizers *NormalizerRegistry
Validators *ValidatorRegistry
ValidatorChains *ValidatorChainRegistry
Outputs *OutputEncoderRegistry
}
func Binding(module string) ModuleBinding {
@@ -251,6 +252,10 @@ func ResolvePipeline(profile PipelineProfile, options ResolveOptions, catalog Mo
if len(steps) == 0 {
return ResolvedPipeline{}, fmt.Errorf("pipeline %q must declare at least one artifact lane", pipelineID)
}
configuredLaneIDs, err := configuredProfileLaneIDs(pipelineID, steps)
if err != nil {
return ResolvedPipeline{}, err
}
input := resolveBinding(profile.Input, "")
if input.Module == "" {
@@ -381,7 +386,7 @@ func ResolvePipeline(profile PipelineProfile, options ResolveOptions, catalog Mo
if missing, ok := outputCapabilities.missing(outputSpec.Requires); ok {
return ResolvedPipeline{}, capabilityError(pipelineID, "", StageOutput, resolved.Output.Module, missing)
}
if err := validateResolvedOptions(resolved, catalog); err != nil {
if err := validateResolvedOptions(resolved, catalog, configuredLaneIDs); err != nil {
return ResolvedPipeline{}, err
}
@@ -393,6 +398,29 @@ func ResolvePipeline(profile PipelineProfile, options ResolveOptions, catalog Mo
return resolved, nil
}
// configuredProfileLaneIDs validates the complete configured lane identity
// set before invocation-level selection removes legacy-profile lanes.
func configuredProfileLaneIDs(pipelineID string, steps []PipelineStepProfile) ([]string, error) {
seen := make(map[string]string)
var laneIDs []string
for _, step := range steps {
_, ids, err := selectedArtifactLanes(pipelineID, step.Artifacts, ResolveOptions{})
if err != nil {
return nil, err
}
stepID := strings.TrimSpace(step.ID)
for _, laneID := range ids {
if previous, ok := seen[laneID]; ok {
return nil, fmt.Errorf("pipeline %q artifact lane id %q is duplicated across steps %q and %q", pipelineID, laneID, previous, stepID)
}
seen[laneID] = stepID
laneIDs = append(laneIDs, laneID)
}
}
sort.Strings(laneIDs)
return laneIDs, nil
}
func resolveArtifactLane(
pipelineID string,
stepID string,
@@ -1190,11 +1218,32 @@ func cloneOptions(options map[string]any) map[string]any {
copied := make(map[string]any, len(options))
for key, value := range options {
copied[key] = value
copied[key] = cloneOptionValue(value)
}
return copied
}
func cloneOptionValue(value any) any {
switch typed := value.(type) {
case map[string]any:
return cloneOptions(typed)
case []any:
out := make([]any, len(typed))
for i := range typed {
out[i] = cloneOptionValue(typed[i])
}
return out
case []string:
return append([]string(nil), typed...)
case []byte:
return append([]byte(nil), typed...)
case json.RawMessage:
return append(json.RawMessage(nil), typed...)
default:
return value
}
}
func normalizeReferenceMap(values map[string]ReferenceSource) map[string]ReferenceSource {
if len(values) == 0 {
return nil