Skip to content

feat(core): Add v1 migration schemas.#794

Open
c-r33d wants to merge 5 commits intomainfrom
migrate-policy
Open

feat(core): Add v1 migration schemas.#794
c-r33d wants to merge 5 commits intomainfrom
migrate-policy

Conversation

@c-r33d
Copy link
Copy Markdown
Contributor

@c-r33d c-r33d commented Apr 7, 2026

Step 1: Artifact Model

Summary

This PR implements step 1 of the policy migration plan.

It adds the initial versioned artifact model for migrate policy-graph output under migrations/artifact, with the current schema implemented as v1.0.0.

Included

  • Shared ArtifactMetadata for:
    • schema
    • name
    • run_id
    • created_at
  • Version-based artifact construction using semver
  • Initial private v1 schema implementation
  • Typed summary model
  • Summary() returning JSON-encoded summary data
  • Write() support on the artifact interface
  • Stubbed Build() and Commit() methods for later steps
  • Unit tests for top-level dispatch and v1 schema behavior

Not Included

  • Build logic
  • Commit logic
  • Prune integration

Testing

go test ./migrations/...

Summary by CodeRabbit

  • New Features

    • Formalized artifact schema/version framework with v1.0.0 as the baseline
    • Standardized artifact lifecycle interface and artifact metadata (schema, name, run ID, timestamp)
    • Defaulting to the current schema version when no version is provided
  • Tests

    • Added comprehensive tests for version selection, v1 schema initialization, summaries, writing, and error cases

@c-r33d c-r33d requested a review from a team as a code owner April 7, 2026 15:26
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Apr 7, 2026

Warning

Rate limit exceeded

@c-r33d has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 5 minutes and 33 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 5 minutes and 33 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: eedd7634-619c-4e4d-ab7f-0327254a7967

📥 Commits

Reviewing files that changed from the base of the PR and between f0efd7a and eef4e89.

📒 Files selected for processing (4)
  • migrations/artifact/artifact.go
  • migrations/artifact/artifact_test.go
  • migrations/artifact/v1/schema.go
  • migrations/artifact/v1/schema_test.go
📝 Walkthrough

Walkthrough

Adds a new migrations/artifact package with a version-aware Artifact constructor and interface, a v1 schema implementation with metadata, serialization and summary behavior, and accompanying tests for version selection, serialization, summaries, and error cases.

Changes

Cohort / File(s) Summary
Core artifact API & tests
migrations/artifact/artifact.go, migrations/artifact/artifact_test.go
Introduce Artifact interface, ArtifactOpts, exported errors (ErrInvalidSchemaVersion, ErrUnsupportedSchemaVersion, ErrNotImplemented), CurrentSchemaVersion, and New() which picks implementation by semver major (defaults to v1). Tests cover unsupported-version rejection, default-to-v1 behavior, Write/Summary contents, and Build/Commit not-implemented errors.
Metadata model
migrations/artifact/metadata/metadata.go
Add ArtifactMetadata struct with JSON fields (schema, name, run_id, created_at), constant ArtifactName = "policy-migration", constructor New(schema, runID, createdAt), and accessors Schema(), Name(), RunID(), CreatedAt().
v1 schema implementation & tests
migrations/artifact/v1/schema.go, migrations/artifact/v1/schema_test.go
Add SchemaVersion = "v1.0.0" and an unexported artifact type with constructor New(writer io.Writer). Implement Build(), Commit() (return not-implemented), Metadata(), Summary() (JSON counts), Write() (indented JSON to writer), summary/update helpers, and errors ErrNilWriter, ErrWriteArtifact, ErrSummaryArtifact. Tests validate init state, Summary counts, Write serialization, and nil-writer error handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Poem

🐰 I rolled a schema, tidy and new,
v1 stamped bright with metadata true,
I count and write with a whiskered grin,
Default the version and let flow begin,
Hop—artifact done, let the migrations run!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding v1 migration schemas to the codebase, which matches the core content of this PR that introduces the migrations/artifact package with v1 schema implementation.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch migrate-policy

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a new artifact migration framework, including metadata management and a versioned schema implementation for policy migrations. I have provided feedback regarding the removal of a redundant constructor call and suggested refactoring the summary count update logic in the Write method to avoid unnecessary JSON marshaling.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🧹 Nitpick comments (1)
migrations/artifact/v1/schema.go (1)

204-205: Avoid marshaling JSON just to refresh summary counts.

Line 204 and Line 255 call Summary() for side-effects and discard bytes. This adds unnecessary work and hides potential future marshal failures at construction time. Consider extracting count calculation into a private helper and using it in both Summary() and Write().

♻️ Proposed refactor
 func New(writer io.Writer) *artifact {
 	doc := &artifact{
 		MetadataData:         artifactmetadata.New(SchemaVersion, uuid.NewString(), time.Now().UTC()),
 		Skipped:              []skippedEntry{},
 		Namespaces:           []namespaceIndexEntry{},
 		Actions:              []actionRecord{},
 		SubjectConditionSets: []subjectConditionSetRecord{},
 		SubjectMappings:      []subjectMappingRecord{},
 		RegisteredResources:  []registeredResourceRecord{},
 		ObligationTriggers:   []obligationTriggerRecord{},
 		writer:               writer,
 	}
-	_, _ = doc.Summary()
+	doc.refreshSummaryCounts()
 	return doc
 }
 
+func (a *artifact) refreshSummaryCounts() {
+	a.SummaryData.Counts = SummaryCounts{
+		Namespaces:           len(a.Namespaces),
+		Actions:              len(a.Actions),
+		SubjectConditionSets: len(a.SubjectConditionSets),
+		SubjectMappings:      len(a.SubjectMappings),
+		RegisteredResources:  len(a.RegisteredResources),
+		ObligationTriggers:   len(a.ObligationTriggers),
+		Skipped:              len(a.Skipped),
+	}
+}
+
 func (a *artifact) Summary() ([]byte, error) {
 	if a == nil {
 		return nil, ErrNilArtifact
 	}
 
-	a.SummaryData.Counts = SummaryCounts{
-		Namespaces:           len(a.Namespaces),
-		Actions:              len(a.Actions),
-		SubjectConditionSets: len(a.SubjectConditionSets),
-		SubjectMappings:      len(a.SubjectMappings),
-		RegisteredResources:  len(a.RegisteredResources),
-		ObligationTriggers:   len(a.ObligationTriggers),
-		Skipped:              len(a.Skipped),
-	}
+	a.refreshSummaryCounts()
 
 	encoded, err := json.Marshal(a.SummaryData)
 	if err != nil {
 		return nil, fmt.Errorf("%w: %w", ErrSummaryArtifact, err)
@@
 	a.normalize()
-	if _, err := a.Summary(); err != nil {
-		return err
-	}
+	a.refreshSummaryCounts()
 
 	encoder := json.NewEncoder(a.writer)
 	encoder.SetIndent("", "  ")

Also applies to: 228-244, 254-257

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/artifact/v1/schema.go` around lines 204 - 205, The Summary()
method is being called for its side-effects (doc.Summary() results discarded),
causing unnecessary JSON marshaling and hiding marshal errors; extract the logic
that computes/refreshes summary counts into a private helper (e.g.,
refreshSummaryCounts or computeCounts) and have both Summary() and Write() call
that helper instead of calling Summary() and discarding bytes; update Summary()
to call the helper then perform marshaling, and update Write() (and any other
sites that call doc.Summary() for side-effects) to call the helper directly
before writing so no redundant json.Marshal occurs and marshal errors surface
where appropriate.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@migrations/artifact/artifact_test.go`:
- Around line 63-69: Replace the two assert.ErrorContains calls with
require.ErrorContains so the test fails fast: after calling doc.Build() and
assigning buildErr (related to doc.Build) use require.ErrorContains(t, buildErr,
"not implemented"); and likewise after doc.Commit() and commitErr (related to
doc.Commit) use require.ErrorContains(t, commitErr, "not implemented").

In `@migrations/artifact/artifact.go`:
- Around line 23-26: The constructor currently allows ArtifactOpts with a nil
Writer which defers failure to later Write calls; update the New function (the
artifact constructor) to validate ArtifactOpts.Writer and return an immediate
error if it's nil (e.g., return nil, fmt.Errorf("writer is required")), and
propagate this change to any callers to handle the error; ensure you also add
the same nil-check wherever the constructor branches by Version (the
version-specific initialization paths referenced in the file) so the writer is
validated centrally on creation.

---

Nitpick comments:
In `@migrations/artifact/v1/schema.go`:
- Around line 204-205: The Summary() method is being called for its side-effects
(doc.Summary() results discarded), causing unnecessary JSON marshaling and
hiding marshal errors; extract the logic that computes/refreshes summary counts
into a private helper (e.g., refreshSummaryCounts or computeCounts) and have
both Summary() and Write() call that helper instead of calling Summary() and
discarding bytes; update Summary() to call the helper then perform marshaling,
and update Write() (and any other sites that call doc.Summary() for
side-effects) to call the helper directly before writing so no redundant
json.Marshal occurs and marshal errors surface where appropriate.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c871ea13-6565-4aa6-b065-85f0759bdd1c

📥 Commits

Reviewing files that changed from the base of the PR and between 8284997 and 9c7bdf0.

📒 Files selected for processing (5)
  • migrations/artifact/artifact.go
  • migrations/artifact/artifact_test.go
  • migrations/artifact/metadata/metadata.go
  • migrations/artifact/v1/schema.go
  • migrations/artifact/v1/schema_test.go

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
migrations/artifact/artifact_test.go (1)

62-68: ⚠️ Potential issue | 🟡 Minor

Replace assert.ErrorContains with require.ErrorContains (lint + fail-fast).

Line 64 and Line 68 still violate testifylint’s require-error rule.

Suggested patch
 	buildErr := doc.Build()
 	require.Error(t, buildErr)
-	assert.ErrorContains(t, buildErr, "not implemented")
+	require.ErrorContains(t, buildErr, "not implemented")
 
 	commitErr := doc.Commit()
 	require.Error(t, commitErr)
-	assert.ErrorContains(t, commitErr, "not implemented")
+	require.ErrorContains(t, commitErr, "not implemented")
#!/bin/bash
# Verify no testifylint require-error violations remain in this file.
rg -n '\bassert\.ErrorContains\s*\(' migrations/artifact/artifact_test.go
# Expected after fix: no output
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/artifact/artifact_test.go` around lines 62 - 68, Replace the two
assert.ErrorContains calls with require.ErrorContains to satisfy testifylint and
fail fast: after calling doc.Build() (buildErr) and doc.Commit() (commitErr)
change assert.ErrorContains(t, buildErr, "not implemented") and
assert.ErrorContains(t, commitErr, "not implemented") to
require.ErrorContains(t, buildErr, "not implemented") and
require.ErrorContains(t, commitErr, "not implemented") respectively so the test
stops immediately on failure and the lint rule is satisfied.
migrations/artifact/artifact.go (1)

36-43: ⚠️ Potential issue | 🟠 Major

Fail fast in New when ArtifactOpts.Writer is nil.

Right now construction succeeds and the caller only fails later on Write(). Validate at creation time to keep the API deterministic.

Suggested patch
 var (
 	currentSchemaVersion = semver.MustParse(CurrentSchemaVersion)

 	ErrInvalidSchemaVersion     = errors.New("invalid artifact schema version")
 	ErrUnsupportedSchemaVersion = errors.New("unsupported artifact schema version")
 	ErrNotImplemented           = errors.New("not implemented")
+	ErrNilWriter                = errors.New("nil artifact writer")
 )
@@
 func New(opts ArtifactOpts) (Artifact, error) {
+	if opts.Writer == nil {
+		return nil, ErrNilWriter
+	}
+
 	version := opts.Version
 	if version == nil {
 		version = currentSchemaVersion
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/artifact/artifact.go` around lines 36 - 43, The constructor New
should fail fast when ArtifactOpts.Writer is nil: add a nil-check at the start
of New that returns a clear error (e.g., "missing writer") rather than allowing
construction to succeed and error later in Write; update New (which currently
calls newDocumentForVersion and uses opts.Writer) to validate opts.Writer != nil
before calling newDocumentForVersion and return the error immediately if nil.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Duplicate comments:
In `@migrations/artifact/artifact_test.go`:
- Around line 62-68: Replace the two assert.ErrorContains calls with
require.ErrorContains to satisfy testifylint and fail fast: after calling
doc.Build() (buildErr) and doc.Commit() (commitErr) change
assert.ErrorContains(t, buildErr, "not implemented") and assert.ErrorContains(t,
commitErr, "not implemented") to require.ErrorContains(t, buildErr, "not
implemented") and require.ErrorContains(t, commitErr, "not implemented")
respectively so the test stops immediately on failure and the lint rule is
satisfied.

In `@migrations/artifact/artifact.go`:
- Around line 36-43: The constructor New should fail fast when
ArtifactOpts.Writer is nil: add a nil-check at the start of New that returns a
clear error (e.g., "missing writer") rather than allowing construction to
succeed and error later in Write; update New (which currently calls
newDocumentForVersion and uses opts.Writer) to validate opts.Writer != nil
before calling newDocumentForVersion and return the error immediately if nil.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8e1e6232-a8c5-459d-950a-a2586351dabb

📥 Commits

Reviewing files that changed from the base of the PR and between 9c7bdf0 and 5bb47b9.

📒 Files selected for processing (5)
  • migrations/artifact/artifact.go
  • migrations/artifact/artifact_test.go
  • migrations/artifact/metadata/metadata.go
  • migrations/artifact/v1/schema.go
  • migrations/artifact/v1/schema_test.go
✅ Files skipped from review due to trivial changes (1)
  • migrations/artifact/v1/schema_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • migrations/artifact/v1/schema.go

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
migrations/artifact/v1/schema.go (2)

217-236: Avoid duplicated summary-count computation paths.

Summary() and updateSummary() currently compute the same counts independently. Centralizing this to one helper reduces drift risk when fields evolve.

♻️ Proposed refactor
+func (a *artifact) computeSummary() Summary {
+	return Summary{
+		Counts: SummaryCounts{
+			Namespaces:           len(a.Namespaces),
+			Actions:              len(a.Actions),
+			SubjectConditionSets: len(a.SubjectConditionSets),
+			SubjectMappings:      len(a.SubjectMappings),
+			RegisteredResources:  len(a.RegisteredResources),
+			ObligationTriggers:   len(a.ObligationTriggers),
+			Skipped:              len(a.Skipped),
+		},
+	}
+}
+
 func (a *artifact) Summary() ([]byte, error) {
-	summary := Summary{
-		Counts: SummaryCounts{
-			Namespaces:           len(a.Namespaces),
-			Actions:              len(a.Actions),
-			SubjectConditionSets: len(a.SubjectConditionSets),
-			SubjectMappings:      len(a.SubjectMappings),
-			RegisteredResources:  len(a.RegisteredResources),
-			ObligationTriggers:   len(a.ObligationTriggers),
-			Skipped:              len(a.Skipped),
-		},
-	}
+	summary := a.computeSummary()
 
 	encoded, err := json.Marshal(summary)
 	if err != nil {
 		return nil, fmt.Errorf("%w: %w", ErrSummaryArtifact, err)
 	}
@@
 func (a *artifact) updateSummary() {
-	a.SummaryData = Summary{
-		Counts: SummaryCounts{
-			Namespaces:           len(a.Namespaces),
-			Actions:              len(a.Actions),
-			SubjectConditionSets: len(a.SubjectConditionSets),
-			SubjectMappings:      len(a.SubjectMappings),
-			RegisteredResources:  len(a.RegisteredResources),
-			ObligationTriggers:   len(a.ObligationTriggers),
-			Skipped:              len(a.Skipped),
-		},
-	}
+	a.SummaryData = a.computeSummary()
 }

Also applies to: 254-266

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/artifact/v1/schema.go` around lines 217 - 236, Summary(): and
updateSummary() duplicate the same count computation; extract that logic into a
single helper (e.g., a method on *artifact named computeSummaryCounts or counts)
that returns SummaryCounts, then call that helper from both Summary() and
updateSummary() so they share the same computation path (update the Summary()
body to use the helper to build Summary{Counts: helper(...)}, and change
updateSummary() to assign from the same helper).

205-211: Use a sentinel for “not implemented” lifecycle methods.

Current string-only errors are fine for now, but a sentinel lets callers use errors.Is(...) reliably when Build/Commit are intentionally unavailable.

♻️ Proposed refactor
 var (
 	ErrNilWriter       = errors.New("nil writer")
 	ErrWriteArtifact   = errors.New("write artifact")
 	ErrSummaryArtifact = errors.New("summary artifact")
+	ErrNotImplemented  = errors.New("not implemented")
 )
@@
 func (a *artifact) Build() error {
-	return fmt.Errorf("artifact build for schema %s: not implemented", SchemaVersion)
+	return fmt.Errorf("artifact build for schema %s: %w", SchemaVersion, ErrNotImplemented)
 }
 
 func (a *artifact) Commit() error {
-	return fmt.Errorf("artifact commit for schema %s: not implemented", SchemaVersion)
+	return fmt.Errorf("artifact commit for schema %s: %w", SchemaVersion, ErrNotImplemented)
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@migrations/artifact/v1/schema.go` around lines 205 - 211, Replace the current
string-only errors in artifact.Build and artifact.Commit with a package-level
sentinel error so callers can reliably use errors.Is; define a var like
ErrNotImplemented = errors.New("not implemented") in the same package and return
wrapped errors from Build and Commit (e.g. fmt.Errorf("artifact build for schema
%s: %w", SchemaVersion, ErrNotImplemented) and similarly for Commit) so the
message retains context while allowing errors.Is to detect the sentinel.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@migrations/artifact/v1/schema.go`:
- Around line 217-236: Summary(): and updateSummary() duplicate the same count
computation; extract that logic into a single helper (e.g., a method on
*artifact named computeSummaryCounts or counts) that returns SummaryCounts, then
call that helper from both Summary() and updateSummary() so they share the same
computation path (update the Summary() body to use the helper to build
Summary{Counts: helper(...)}, and change updateSummary() to assign from the same
helper).
- Around line 205-211: Replace the current string-only errors in artifact.Build
and artifact.Commit with a package-level sentinel error so callers can reliably
use errors.Is; define a var like ErrNotImplemented = errors.New("not
implemented") in the same package and return wrapped errors from Build and
Commit (e.g. fmt.Errorf("artifact build for schema %s: %w", SchemaVersion,
ErrNotImplemented) and similarly for Commit) so the message retains context
while allowing errors.Is to detect the sentinel.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 91494f00-caf3-47f1-9920-5a9d9cfe4bf0

📥 Commits

Reviewing files that changed from the base of the PR and between 5bb47b9 and f0efd7a.

📒 Files selected for processing (2)
  • migrations/artifact/v1/schema.go
  • migrations/artifact/v1/schema_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
  • migrations/artifact/v1/schema_test.go

@github-actions
Copy link
Copy Markdown

github-actions bot commented Apr 7, 2026

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant