Skip to content

feat(client/v1): wrap remaining facade alias types (Recipe, AllowLists, Criteria, CriteriaRegistry) #1115

Description

@mchmarny

Summary

Complete the facade-ownership story started by #1078 by replacing the four remaining transparent type aliases in pkg/client/v1/types.go (Recipe, AllowLists, Criteria, CriteriaRegistry) with facade-owned structs and translation funcs at the boundary.

Motivation

pkg/client/v1 is the Public (stable) facade per docs/integrator/public-api.md, but four of its types remain transparent aliases into pkg/recipe (Public (evolving)):

type Recipe           = recipe.RecipeResult
type AllowLists       = recipe.AllowLists
type Criteria         = recipe.Criteria
type CriteriaRegistry = recipe.CriteriaRegistry

This is the same anti-pattern #1078 fixed for Snapshot / AgentConfig / PhaseResult / Phase: any field-shape change in pkg/recipe propagates to external Go importers without a minor-version signal, undermining the semver promise.

#1078 was originally scoped to the snapshotter/validator-tier aliases only. The recipe-tier aliases were added later by #1077 (the CLI/API migration PR) and tracked here for follow-up.

Complication — existing dual surface

The facade today has both:

  • aicr.Recipe = recipe.RecipeResult — transparent alias used by ResolveRecipeFromCriteria, ResolveRecipeFromSnapshot, LoadRecipe, AdoptRecipe, SelectFromRecipe
  • aicr.RecipeResult struct { … internal *recipe.RecipeResult … } — facade-owned wrapper used by ResolveRecipe, BundleComponents, ValidateState

The two coexist because they were added in different PRs (RecipeResult by #1072; Recipe alias by #1077). They overlap functionally but expose different fields.

Resolving this means picking one shape, not just adding a wrapper. Options:

  1. Consolidate on RecipeResult (facade-owned struct). Delete the Recipe alias; migrate methods that returned *Recipe to return *RecipeResult. Translation at the boundary populates the public fields from the internal *recipe.RecipeResult.
  2. Consolidate on Recipe (now as a facade-owned struct). Delete RecipeResult; migrate the existing methods. Recipe would carry both the public payload and an unexported internal *recipe.RecipeResult field.

Either way, this is a bigger change than just dropping the = aliases.

Type-by-type sketch

Type Underlying Wrap shape
Recipe (or RecipeResult) recipe.RecipeResult (~10 fields, sub-types: Constraint, ComponentRef, ValidationConfig, ExcludedOverlay, ConstraintWarning) Pick the dual-surface resolution above. Mirror public fields; hold internal *recipe.RecipeResult for zero-copy round-trip and the ownership/provider tokens.
AllowLists recipe.AllowLists (~5 string-slice fields) Mirror fields; translate at WithAllowLists / ParseAllowListsFromEnv.
Criteria recipe.Criteria (~7 enum fields) Mirror fields; translate at REST parseQuery / parseBody, ResolveRecipeFromCriteria, SelectFromRecipe.
CriteriaRegistry recipe.CriteriaRegistry (complex internal state, methods: Has, Values, ParseService etc.) Trickiest — the registry has behavior, not just data. Two options: (a) re-expose a minimal interface; (b) keep as alias indefinitely and document why.

CriteriaRegistry may be the right candidate to keep aliased — it's a behavior-rich type that an external importer holding a *Builder needs the same one of. Wrapping it loses the per-provider identity link.

Acceptance

  • Dual-surface resolution decided (consolidate Recipe vs. RecipeResult); decision documented in PR description.
  • Recipe / AllowLists / Criteria are facade-owned structs in pkg/client/v1/types.go; aliases removed.
  • CriteriaRegistry either wrapped or explicitly documented as a behavior-rich type kept as alias intentionally.
  • Translation funcs in pkg/client/v1/translate.go (extend the existing file).
  • All facade methods that returned/accepted the aliased types updated to use facade-owned shapes; translation happens at each boundary.
  • In-tree callers (pkg/cli, pkg/api) updated to consume the new shapes.
  • docs/integrator/public-api.md "Facade type ownership" table updated.
  • docs/integrator/go-library.md examples updated.
  • make qualify clean.

Risk / size

High — ~1500-2000 LOC est. Recipe is the central type; the dual-surface resolution alone deserves its own design discussion before code changes.

Out of scope

Related

Metadata

Metadata

Assignees

Type

No type

Fields

No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions