Instance Builders
Instance builders extend the spec builder pattern to programmatically create and manage object instances. Unlike spec builders (one per spec), there's ONE instance builder per object type that handles ALL instances of that type.
Overview
Instance builders provide:
- Code-driven instance creation: Create instances programmatically (fluent API)
- YAML file support: Load and write instances from/to YAML files
- Sequence file support: Compact storage format (values in order, no field names)
- Version-aware: Support different schema versions; use
objects.DefaultSchemaVersionfor the project default - Storage efficiency: Sequence format significantly reduces file size
Key Distinction
| Type | Count | Builder Pattern |
|---|---|---|
| Specs | ONE per object type | One spec builder per spec version |
| Instances | MANY per object type | ONE instance builder per object type |
Example:
- Policy spec: ONE spec β ONE
PolicySpecBuilder - Policy instances: MANY (POL-EXAMPLE-001, POL-EXAMPLE-002, etc.) β ONE
PolicyInstanceBuilder
Architecture
Core Components
- BaseInstanceBuilder: Base class providing common functionality
- InstanceBuilder: Interface that all instance builders implement
- VersionedInstanceBuilderRegistry: Registry for managing builders by kind and version
- InstanceGenerator: Generates YAML instance files from builders
Key Differences from Spec Builders
| Aspect | Spec Builders | Instance Builders |
|---|---|---|
| Output Type | *objects.Spec |
map[string]any |
| Fields | Schema definition | Instance data (values) |
| Versioning | Builder version (v1_0_0) | Instance schema_version (1.0.0) |
| Purpose | Define object structure | Create object data |
Usage
Creating Instances Programmatically
import "github.com/zqk-os/zqk/pkg/objects"
// Create a policy instance builder (ONE builder for ALL policy instances)
builder := NewPolicyInstanceBuilder(objects.DefaultSchemaVersion)
instance, err := builder.
SetID("POL-EXAMPLE-001").
SetTitle("Code Quality Maintenance").
SetCategory("code_quality").
SetStatus("active").
Build()
Loading from YAML
import "github.com/zqk-os/zqk/pkg/objects"
builder := NewPolicyInstanceBuilder(objects.DefaultSchemaVersion)
instance, err := builder.LoadFromYAML(".zqk/process/policies/POL-EXAMPLE-001.yaml")
Loading from Sequence File (Compact Format)
import "github.com/zqk-os/zqk/pkg/objects"
builder := NewPolicyInstanceBuilder(objects.DefaultSchemaVersion)
instance, err := builder.LoadFromSequence(".zqk/process/policies/POL-EXAMPLE-001.seq")
Writing to Compact Format
import "github.com/zqk-os/zqk/pkg/objects"
builder := NewPolicyInstanceBuilder(objects.DefaultSchemaVersion)
instance := map[string]any{
"id": "POL-EXAMPLE-001",
"title": "Code Quality Maintenance",
// ...
}
err := builder.WriteToSequence(instance, "policies/POL-EXAMPLE-001.seq")
Version-Aware Creation
import "github.com/zqk-os/zqk/pkg/objects"
// Create instance for an older spec schema (example)
builder1 := NewPolicyInstanceBuilder("1.0.0")
instance1, _ := builder1.SetID("POL-001").Build()
// Create instance for the current default schema
builder2 := NewPolicyInstanceBuilder(objects.DefaultSchemaVersion)
instance2, _ := builder2.SetID("POL-002").Build()
Using Registry
import "github.com/zqk-os/zqk/pkg/objects"
// Get builder from registry
registry := GetGlobalRegistry()
builder, err := registry.GetBuilder("policy", objects.DefaultSchemaVersion)
instance, err := builder.Build()
Implementation Pattern
Creating an Instance Builder
In this repo, instance builders are generated (see βCode Generationβ above). Do not hand-write new builders in bldr_instance_v1/; add or change the object spec and run zqk system generate-instance-builders --overwrite. The following describes the structure the generator produces:
- Create a builder struct embedding
BaseInstanceBuilder - Create constructor that initializes base builder
- Add fluent methods for setting fields (generated from spec)
- Register builder in
init()function
Example Structure
package instance_builders
type PolicyInstanceBuilder struct {
*BaseInstanceBuilder
}
func NewPolicyInstanceBuilder(schemaVersion string) *PolicyInstanceBuilder {
// Optionally get spec builder for validation/structure
var specBuilder SpecBuilderInterface = nil // Can be nil
builder := &PolicyInstanceBuilder{
BaseInstanceBuilder: NewBaseInstanceBuilder("policy", schemaVersion, specBuilder),
}
// Set defaults
builder.SetField("status", "active")
builder.SetField("origin_system", "zqk")
return builder
}
func (b *PolicyInstanceBuilder) SetTitle(title string) *PolicyInstanceBuilder {
b.SetField("title", title)
return b
}
func (b *PolicyInstanceBuilder) SetCategory(category string) *PolicyInstanceBuilder {
b.SetField("category", category)
return b
}
Integration with Spec Builders
Instance builders can optionally reference spec builders to:
- Validate field names and types
- Apply default values from spec definitions
- Ensure instances conform to spec structure
The spec builder reference is optional - instance builders can work standalone.
Code Generation (Generated Files)
All files in pkg/specbuilder/bldr_instance_v1/*_instance_builder.go are generated from object spec YAML. Do not edit them by hand; changes will be overwritten on the next run.
- Source: Object spec YAML files (e.g.
.zqk/specs/objects/*.yamlor bldr_v2-backed specs). - Generator:
pkg/specbuilder/instance_builders/codegen.goβGenerateInstanceBuilderFromSpec(specPath, outputDir, schemaVersion). - Regenerate: Run
zqk system generate-instance-builders(optionally--specs-dir,--output-dir,--overwrite).
Generated files include a header: // Code generated by ... DO NOT EDIT. Regenerate with: zqk system generate-instance-builders --overwrite.
Required Pattern: Creating Objects in Code
When creating metric or other spec-backed objects (e.g. in storage, scheduler, or CLI):
- Use the instance builder for that kind (e.g.
bldr_instance_v1.NewBaseMetricInstanceBuilder,NewAuditAggregationMetricInstanceBuilder,NewSchedulerHealthMetricInstanceBuilder). Do not build literalmap[string]anyfor objects that have a generated builder. - Set lifecycle-valid status: Use a status value allowed by that kindβs lifecycle (e.g.
audit_aggregation_metric:completed,archived,error;scheduler_health_metric:active,archived,error). Checkpkg/specbuilder/bldr_lifecycle_v1/*_builder.goor lifecycle YAML. - Use typed setters where the generated builder exposes them; use
SetField(name, value)for inherited or extra fields (e.g. base_metric fields on a child metric builder that has no dedicated setter). - Call
Build()and pass the result to storage/create; handle build errors before persisting.
Example (metric creation):
builder := bldr_instance_v1.NewAuditAggregationMetricInstanceBuilder(objects.DefaultSchemaVersion)
builder.SetID(metricID)
builder.SetStatus("completed") // lifecycle-valid for audit_aggregation_metric
builder.SetField("title", title)
// ... other fields ...
obj, err := builder.Build()
if err != nil { return err }
return storage.Create(ctx, secCtx, obj)
Status
Current Status: Core infrastructure and codegen in place
- β BaseInstanceBuilder
- β InstanceBuilder interface
- β VersionedInstanceBuilderRegistry
- β InstanceGenerator
- β
Code generation from spec YAML (
GenerateInstanceBuilderFromSpec) - β
CLI:
zqk system generate-instance-builders - β
Generated builders in
pkg/specbuilder/bldr_instance_v1/
Convergence / consistency (ongoing)
Aligned with active convergence work on pipeline and DRY:
- Field typing and validation:
BaseInstanceBuildercentralizes field-type hints (fieldTypeEnum,fieldTypeObject, β¦), YAML extension (.yaml), and validation keys (validation,required,enum) inbase_builder.go; prefer extending those constants over new literals in hand-maintained code. - Map handling:
maps.Copyfrom the standard library is used when merging or cloningmap[string]anypayloads; avoid ad-hocfor k, v := rangecopy loops when types match. - Gaps: New object kinds should get generated instance builders when possible; one-off
map[string]anyconstruction for spec-backed kinds is a known gapβcallGenerateInstanceBuilderFromSpec/zqk system generate-instance-buildersinstead of growing literals.