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

@@ -28,6 +28,7 @@ var _ contracts.OutputEncoder = (*Encoder)(nil)
type Options struct {
IncludeChunkMap bool
EvidenceContext pipeline.EvidenceContextPolicy
}
type Encoder struct {
@@ -39,6 +40,7 @@ func New() *Encoder {
}
func NewWithOptions(options Options) *Encoder {
options.EvidenceContext.LaneIDs = append([]string(nil), options.EvidenceContext.LaneIDs...)
return &Encoder{options: options}
}
@@ -46,6 +48,15 @@ func (e *Encoder) Key() string {
return Key
}
func (e *Encoder) EvidenceContextPolicy() pipeline.EvidenceContextPolicy {
if e == nil {
return pipeline.EvidenceContextPolicy{}
}
policy := e.options.EvidenceContext
policy.LaneIDs = append([]string(nil), policy.LaneIDs...)
return policy
}
func (e *Encoder) Encode(ctx context.Context, req contracts.OutputRequest) (contracts.OutputResult, error) {
if e == nil {
return contracts.OutputResult{}, encoderErrorf("encoder must not be nil")
@@ -74,7 +85,7 @@ func ModuleSpec() pipeline.ModuleSpec {
}
func Register(registry *pipeline.OutputEncoderRegistry) error {
return registry.RegisterBuilderWithSpec(ModuleSpec(), validateOptions, func(request pipeline.BuildRequest) (contracts.OutputEncoder, error) {
return registry.RegisterBuilderWithProfileValidation(ModuleSpec(), validateOptions, validateProfileOptions, func(request pipeline.BuildRequest) (contracts.OutputEncoder, error) {
options, err := DecodeOptions(request.Options)
if err != nil {
return nil, err
@@ -89,17 +100,126 @@ func validateOptions(options map[string]any) error {
}
func DecodeOptions(options map[string]any) (Options, error) {
if err := pipeline.RejectUnknownOptions(options, "include_chunk_map"); err != nil {
if err := pipeline.RejectUnknownOptions(options, "include_chunk_map", "evidence_context"); err != nil {
return Options{}, encoderErrorf("%w", err)
}
decoded := Options{}
if value, ok := options["include_chunk_map"]; ok {
enabled, ok := value.(bool)
if !ok {
return Options{}, encoderErrorf("option %q must be a boolean", "include_chunk_map")
}
return Options{IncludeChunkMap: enabled}, nil
decoded.IncludeChunkMap = enabled
}
return Options{}, nil
if value, ok := options["evidence_context"]; ok {
policy, err := decodeEvidenceContextPolicy(value)
if err != nil {
return Options{}, err
}
decoded.EvidenceContext = policy
}
return decoded, nil
}
func validateProfileOptions(context pipeline.OutputProfileOptionContext, options map[string]any) error {
decoded, err := DecodeOptions(options)
if err != nil || !decoded.EvidenceContext.Enabled {
return err
}
configured := make(map[string]struct{}, len(context.LaneIDs))
for _, laneID := range context.LaneIDs {
configured[laneID] = struct{}{}
}
for _, laneID := range decoded.EvidenceContext.LaneIDs {
if _, ok := configured[laneID]; !ok {
return encoderErrorf("evidence_context lane %q is not configured", laneID)
}
}
return nil
}
func decodeEvidenceContextPolicy(value any) (pipeline.EvidenceContextPolicy, error) {
object, ok := value.(map[string]any)
if !ok {
return pipeline.EvidenceContextPolicy{}, encoderErrorf("option %q must be an object", "evidence_context")
}
if err := pipeline.RejectUnknownOptions(object, "enabled", "lanes", "window_units"); err != nil {
return pipeline.EvidenceContextPolicy{}, encoderErrorf("evidence_context: %w", err)
}
enabledValue, ok := object["enabled"]
if !ok {
return pipeline.EvidenceContextPolicy{}, encoderErrorf("evidence_context option %q is required", "enabled")
}
enabled, ok := enabledValue.(bool)
if !ok {
return pipeline.EvidenceContextPolicy{}, encoderErrorf("evidence_context option %q must be a boolean", "enabled")
}
if !enabled {
if _, ok := object["lanes"]; ok {
return pipeline.EvidenceContextPolicy{}, encoderErrorf("evidence_context option %q is not allowed when disabled", "lanes")
}
if _, ok := object["window_units"]; ok {
return pipeline.EvidenceContextPolicy{}, encoderErrorf("evidence_context option %q is not allowed when disabled", "window_units")
}
return pipeline.EvidenceContextPolicy{}, nil
}
rawLanes, ok := object["lanes"]
if !ok {
return pipeline.EvidenceContextPolicy{}, encoderErrorf("evidence_context option %q is required when enabled", "lanes")
}
lanes, err := decodeEvidenceLaneIDs(rawLanes)
if err != nil {
return pipeline.EvidenceContextPolicy{}, err
}
windowUnits := 3
if rawWindow, ok := object["window_units"]; ok {
value, ok := rawWindow.(int)
if !ok {
return pipeline.EvidenceContextPolicy{}, encoderErrorf("evidence_context option %q must be an integer", "window_units")
}
if value < 0 {
return pipeline.EvidenceContextPolicy{}, encoderErrorf("evidence_context option %q must not be negative", "window_units")
}
windowUnits = value
}
return pipeline.EvidenceContextPolicy{Enabled: true, WindowUnits: windowUnits, LaneIDs: lanes}, nil
}
func decodeEvidenceLaneIDs(value any) ([]string, error) {
var raw []any
switch typed := value.(type) {
case []any:
raw = typed
case []string:
raw = make([]any, len(typed))
for i := range typed {
raw[i] = typed[i]
}
default:
return nil, encoderErrorf("evidence_context option %q must be an array", "lanes")
}
if len(raw) == 0 {
return nil, encoderErrorf("evidence_context option %q must not be empty", "lanes")
}
seen := make(map[string]struct{}, len(raw))
lanes := make([]string, 0, len(raw))
for _, value := range raw {
lane, ok := value.(string)
if !ok {
return nil, encoderErrorf("evidence_context lane values must be strings")
}
lane = strings.TrimSpace(lane)
if lane == "" {
return nil, encoderErrorf("evidence_context lane values must not be empty")
}
if _, ok := seen[lane]; ok {
return nil, encoderErrorf("evidence_context lane %q is duplicated", lane)
}
seen[lane] = struct{}{}
lanes = append(lanes, lane)
}
sort.Strings(lanes)
return lanes, nil
}
type indexFile struct {