OCI bundle format for model weights (#2676)
* feat(model): add ModelFormat and Index types for OCI bundle support
* feat(model): add WeightsManifest and WeightFile types
* feat(model): add WeightsLock file format for weight metadata
* feat(tools): add weights-lock-gen for testing OCI bundle format
* feat(ociartifact): add weights artifact builder using go-containerregistry
Implements WeightsArtifactBuilder for creating OCI artifacts containing model
weights. The builder:
- Creates OCI-format manifests (application/vnd.oci.image.manifest.v1+json)
- Sets artifactType to application/vnd.cog.weights.v1
- Adds layers with custom media types and annotations for weight files
- Uses mutate.Addendum to properly attach layer annotations
Layer annotations include:
- vnd.cog.weights.name: original filename
- vnd.cog.weights.dest: container path
- vnd.cog.weights.source: origin URL (optional)
- vnd.cog.weights.digest.original: uncompressed digest
- vnd.cog.weights.size.uncompressed: uncompressed size
Test coverage: 93.8%
* feat(ociartifact): add AddLayersFromLock for file-based weight loading
* feat(model): add OCIIndex and WeightsLockPath build options
* feat(model): add OCI index detection helpers in resolver
Add helper functions to detect and handle OCI indexes:
- isOCIIndex: checks if ManifestResult is an OCI Image Index
- findWeightsManifest: finds weights manifest by annotation
- findImageManifest: finds model image manifest with platform filtering
Also:
- Add Annotations field to PlatformManifest struct
- Populate annotations when reading OCI index manifests
- Add PushImage/PushIndex to registry Client interface for OCI index push support
* feat(model): consolidate OCI index builders and wire push flow
- Move WeightsArtifactBuilder, IndexBuilder, IndexFactory from pkg/ociartifact to pkg/model/index_factory.go
- Delete pkg/ociartifact/ package (code duplication eliminated)
- Add OCI index push flow in pkg/cli/push.go (COG_OCI_INDEX=1 env var)
- Add index detection helpers in resolver for loading OCI indexes
- Simplify BuildOptions: remove WeightsLockPath (hardcoded to weights.lock)
- Add Platform.Variant field to pkg/model/image.go
- Add push_test.go placeholder for registry push tests
* feat(weights-lock-gen): generate random weight files for testing
Replace the --files flag with random file generation:
- Add --count (-n) flag for number of files (default: 3)
- Add --min-size and --max-size flags (default: 25mb-50mb)
- Add --output-dir to optionally keep generated files
- Files are named weights-001.bin, weights-002.bin, etc.
- Generate random data in 1MB chunks for memory efficiency
This simplifies testing the OCI bundle format without needing
real weight files.
* feat(model): refactor OCI index push into Resolver.Push()
- Add ModelImageFormat enum (FormatStandalone, FormatBundle) in format.go
- Update Model struct: replace Format with ImageFormat field
- Update BuildOptions: replace OCIIndex bool with ImageFormat
- Create Pusher interface with ImagePusher and BundlePusher implementations
- Add Resolver.Push() that delegates to appropriate pusher based on format
- Add BuildWeightsArtifactFromManifest to IndexFactory
- Simplify pkg/cli/push.go to use resolver.Push() for bundles
- Move ImageFormatFromEnv() to model package
The push logic is now cleanly separated:
- ImagePusher: Simple docker push for standalone images
- BundlePusher: Full OCI Index workflow (push image, build weights
artifact, build index, push index)
Closes cog-cz1
* feat(integration-tests): add mock registry and OCI bundle tests
- Add inline mock weights generator to avoid pkg/wheels init panic
- Add StartTestRegistryWithCleanup for harness (no *testing.T required)
- Add registry-start, registry-inspect, docker-push, mock-weights commands
- Add oci_bundle_build.txtar and oci_bundle_push.txtar integration tests
- Update push_test.go to use testcontainers instead of TEST_REGISTRY env
* chore: add test weight files to gitignore
* fix(weights-lock-gen): suppress gosec warning for test data RNG
* refactor(weights): remove Source field and use filePaths map for weight resolution
- Remove Source field from WeightFile - lockfile maps name→blob, not source
- Change Name semantics to identifier/handle (e.g., 'model-v1') not filename
- Add filePaths map[string]string parameter to AddLayersFromLock for file location
- Add context cancellation support to AddLayersFromLock for long operations
- Add AnnotationValueWeights constant for 'weights' annotation value
- Improve registry push error messages to include reference
- Remove binary test files (~109MB) that were accidentally committed
- Update all tests to use new API and identifier-style names
* refactor: replace magic strings with constants for platform and weights
- Add PlatformUnknown constant for non-platform-specific artifacts
- Use AnnotationValueWeights constant instead of "weights" string literal
- Update all usages in index_factory.go, resolver.go, and tests