Add pkg/model Resolver API for build encapsulation (#2663)
* Add pkg/model with ParsedRef for image reference parsing
Introduces the model package as the foundation for the Build API
Encapsulation epic (cog-fuo). This first phase adds:
- ParsedRef: wrapper around go-containerregistry/pkg/name.Reference
- ParseRef(): validates and parses image references with options
- ParseOption functions: Insecure(), WithDefaultRegistry(), WithDefaultTag()
- Methods: Registry(), Repository(), Tag(), Digest(), IsTag(), IsDigest(), IsReplicate()
The lazy method design queries the underlying reference on-demand,
ensuring IsReplicate() always reflects the current registry host setting.
* Add pkg/model/image.go with Image struct and Cog label helpers
* Add pkg/model/model.go with Model struct and related types
* Add pkg/model Source and BuildOptions types
Source wraps config + projectDir for build inputs.
BuildOptions consolidates all build flags with WithDefaults().
* Add pkg/model Factory interface and DockerfileFactory
Factory interface enables pluggable build backends.
DockerfileFactory wraps existing image.Build() function.
* Add pkg/model/resolver.go with Resolver struct and Load API
- Resolver orchestrates building and loading Models
- Load() with functional options: LocalOnly, RemoteOnly, PreferRemote, WithPlatform
- LoadByID() for stable image ID-based lookups (short or full SHA)
- Build() delegates to Factory and inspects result
- Default PreferLocal behavior with smart fallback (only on not-found errors)
- Comprehensive tests with mocked docker/registry
* Add pkg/model/ref_types.go with Ref interface and declarative resolution
Implements the Ref pattern for declarative model resolution:
- Ref interface with internal resolve() method
- TagRef/FromTag: resolves via Load (local-first, fallback to remote)
- LocalRef/FromLocal: resolves from local docker daemon only
- RemoteRef/FromRemote: resolves from remote registry only
- BuildRef/FromBuild: resolves by building from Source
- Resolver.Resolve(ctx, ref) dispatches to appropriate resolution
* Return image ID from Build() for stable lookups
- ImageBuild now returns (string, error) with manifest digest
- image.Build() returns the final image ID from label-adding step
- DockerfileFactory.Build() populates Image.Digest
- Resolver.Build() uses digest for inspection, falls back to tag
This fixes instability where tag resolution could pull from remote
registry instead of using the locally-built image.
* Migrate pkg/cli/build.go to use model.Resolver
Refactor build.go to use the new model.Resolver.Build() API instead of
directly calling image.Build() with 19 parameters. This encapsulates
build complexity behind the Resolver abstraction.
Changes:
- Replace config.GetConfig() with model.NewSource()
- Replace image.Build() call with resolver.Build() using BuildOptions
- Use m.ImageRef() for output message instead of input imageName
- Add defensive nil checks for src.Config.Build
* Refine Resolver API: rename Load→Inspect, add Pull(), validate Cog models
- Rename Load() to Inspect() for clearer semantics (metadata only, no pull)
- Add Pull() method that ensures image is locally available
- Add sentinel errors: ErrNotCogModel, ErrNotFound
- Validate Cog model labels in both local and remote inspection
- Update registry.Inspect() to fetch config blob for labels
- Support multi-platform indexes by picking default image for labels
- Migrate CLI commands (predict, train) to use resolver.Pull()
- Add BuildBase support for dev mode base image builds
* Add parsed label accessors to Image with proper error propagation
- Add Image.ParsedConfig() - parses cog config JSON from labels
- Add Image.ParsedOpenAPISchema() - parses OpenAPI schema from labels
- Add Image.ToModel() - converts Image to Model by parsing labels
- Refactor resolver to use new methods, eliminating duplicate parsing code
- Fix: schema parsing errors now propagate instead of being swallowed
* Address code review feedback for Resolver API
- Remove redundant inspectLocal wrapper in Pull()
- Drop unused Model fields (Weights, Runtime, GitCommit, GitTag)
- Fix pickDefaultImage to log errors instead of silently swallowing
- Deprecate Fast field in BuildOptions (read from config at build time)
- Fix test mocks to return errors instead of panic
* Address Copilot code review feedback
- Pass context to pickDefaultImage for cancellation support
- Add name.Insecure option to NewDigest for HTTP registry compatibility
- Validate ImageBuild returns non-empty image ID from buildkit
- Fix comment: org.cogmodel.config -> run.cog.config
- Add nil validation to Resolver.Build/BuildBase methods
* Improve error handling: distinguish not-found from other failures
- loadLocal/loadRemote now return accurate error messages:
- "not found" only for actual not-found errors
- "failed to inspect" for auth, network, permission errors
- pickDefaultImage returns (v1.Image, error) instead of silently
returning nil on failures
- Multi-platform index label fetch now propagates errors
- Updated tests to verify new error message format