diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 906a328..3f50c95 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -432,7 +432,7 @@ jobs: go-version-file: go.mod - name: Run golangci-lint - uses: golangci/golangci-lint-action@v6 + uses: golangci/golangci-lint-action@v9 with: version: latest args: --timeout=5m diff --git a/cmd/mpe/main.go b/cmd/mpe/main.go index 0443725..9d37719 100644 --- a/cmd/mpe/main.go +++ b/cmd/mpe/main.go @@ -212,6 +212,10 @@ func main() { Name: "no-opa-flags", Usage: "Disable all OPA flags (overrides --opa-flags and MPE_CLI_OPA_FLAGS).", }, + &cli.BoolFlag{ + Name: "regal", + Usage: "Run Regal linting instead of standard validation. Uses the bundled Regal library to check embedded Rego code against Regal's rule set.", + }, }, Action: lint.Execute, }, diff --git a/cmd/mpe/subcommands/build/core.go b/cmd/mpe/subcommands/build/core.go index 1780bfd..d846378 100644 --- a/cmd/mpe/subcommands/build/core.go +++ b/cmd/mpe/subcommands/build/core.go @@ -111,7 +111,7 @@ func File(inputFile, outputFile string) Result { return result } - if err := processYAMLNode(&rootNode); err != nil { + if err := processYAMLNode(&rootNode, ""); err != nil { result.Error = err return result } @@ -160,14 +160,21 @@ func IsPolicyDomainReference(filePath string) (bool, error) { return doc.Kind == "PolicyDomainReference", nil } -func processYAMLNode(node *yaml.Node) error { +// regoRequiredParents defines the YAML keys whose child items must have 'rego' or 'rego_filename'. +var regoRequiredParents = map[string]bool{ + "policies": true, + "policy-libraries": true, + "mappers": true, +} + +func processYAMLNode(node *yaml.Node, parentKey string) error { if node == nil { return nil } if node.Kind == yaml.DocumentNode { for _, child := range node.Content { - if err := processYAMLNode(child); err != nil { + if err := processYAMLNode(child, parentKey); err != nil { return err } } @@ -175,12 +182,12 @@ func processYAMLNode(node *yaml.Node) error { } if node.Kind == yaml.MappingNode { - return processMappingNode(node) + return processMappingNode(node, parentKey) } if node.Kind == yaml.SequenceNode { for _, item := range node.Content { - if err := processYAMLNode(item); err != nil { + if err := processYAMLNode(item, parentKey); err != nil { return err } } @@ -190,7 +197,7 @@ func processYAMLNode(node *yaml.Node) error { return nil } -func processMappingNode(node *yaml.Node) error { +func processMappingNode(node *yaml.Node, parentKey string) error { if len(node.Content)%2 != 0 { return fmt.Errorf("invalid YAML mapping node") } @@ -217,7 +224,12 @@ func processMappingNode(node *yaml.Node) error { } } - if err := processYAMLNode(valueNode); err != nil { + // Pass the current key as parentKey so children know their context + currentKey := "" + if keyNode.Kind == yaml.ScalarNode { + currentKey = keyNode.Value + } + if err := processYAMLNode(valueNode, currentKey); err != nil { return err } } @@ -226,6 +238,11 @@ func processMappingNode(node *yaml.Node) error { return fmt.Errorf("cannot specify both 'rego' and 'rego_filename' in the same block") } + // If this node is inside a rego-bearing section, it must have rego or rego_filename + if regoRequiredParents[parentKey] && !hasRego && !hasRegoFilename { + return fmt.Errorf("missing 'rego' or 'rego_filename' in '%s' entry", parentKey) + } + if hasRegoFilename { if regoFilenameValue == "" { return fmt.Errorf("rego_filename cannot be empty") diff --git a/cmd/mpe/subcommands/build/core_test.go b/cmd/mpe/subcommands/build/core_test.go index 3af2381..5dc5bc9 100644 --- a/cmd/mpe/subcommands/build/core_test.go +++ b/cmd/mpe/subcommands/build/core_test.go @@ -165,6 +165,20 @@ func TestBuildFile_MissingRegoErrorCase(t *testing.T) { // Error encountered should be "failed to read rego file '/nonexistent/path/missing.rego'" assert.Contains(t, result.Error.Error(), "failed to read rego file '/nonexistent/path/missing.rego'") } +func TestBuildFile_NoRegoInPolicyErrorCase(t *testing.T) { + inputFile := createTempFileFromTestData(t, "error-no-rego-in-policy.yml") + + result := File(inputFile, "") + + // Build should fail + assert.False(t, result.Success, "Build should fail") + assert.NotNil(t, result.Error, "Should have error") + + // Error encountered should indicate missing rego + assert.Contains(t, result.Error.Error(), "missing 'rego' or 'rego_filename'") + assert.Contains(t, result.Error.Error(), "policies") +} + func TestBuildFile_EmptyRegoErrorCase(t *testing.T) { inputFile := createTempFileFromTestData(t, "error-empty-rego.yml") diff --git a/cmd/mpe/subcommands/build/test/error-no-rego-in-policy.yml b/cmd/mpe/subcommands/build/test/error-no-rego-in-policy.yml new file mode 100644 index 0000000..8b947fe --- /dev/null +++ b/cmd/mpe/subcommands/build/test/error-no-rego-in-policy.yml @@ -0,0 +1,10 @@ +apiVersion: iamlite.manetu.io/v1alpha3 +kind: PolicyDomainReference +metadata: + name: error-test +spec: + policies: + - mrn: "mrn:iam:policy:missing-rego" + name: missing-rego + description: "This policy has neither rego nor rego_filename - should error" + diff --git a/cmd/mpe/subcommands/lint/core.go b/cmd/mpe/subcommands/lint/core.go index bc96ff0..9bc4cff 100644 --- a/cmd/mpe/subcommands/lint/core.go +++ b/cmd/mpe/subcommands/lint/core.go @@ -54,6 +54,23 @@ func Execute(ctx context.Context, cmd *cli.Command) error { } files = processedFiles + // If --regal is supplied, run only Regal linting + if cmd.Bool("regal") { + fmt.Println("Running Regal linting...") + fmt.Println() + + regalErrors := performRegalLinting(ctx, files) + + fmt.Println("---") + if regalErrors > 0 { + fmt.Printf("Regal linting completed: %d violation(s)\n", regalErrors) + return fmt.Errorf("regal linting failed: %d violation(s)", regalErrors) + } + + fmt.Printf("Regal linting passed: %d file(s) validated successfully\n", len(files)) + return nil + } + // Get OPA flags from command line, environment variable, or use default noOpaFlags := cmd.Bool("no-opa-flags") opaFlags := cmd.String("opa-flags") diff --git a/cmd/mpe/subcommands/lint/core_test.go b/cmd/mpe/subcommands/lint/core_test.go index 602db08..ce44084 100644 --- a/cmd/mpe/subcommands/lint/core_test.go +++ b/cmd/mpe/subcommands/lint/core_test.go @@ -5,6 +5,7 @@ package lint import ( + "context" "os" "path/filepath" "testing" @@ -235,3 +236,144 @@ func TestLintFile_FailOpaCheck(t *testing.T) { errorCount := lintRegoUsingExistingValidation([]string{validFile}, "--v0-compatible") assert.Greater(t, errorCount, 0, "Should have OPA check errors (undefined function)") } + +// ============================================================================= +// Regal linting tests +// ============================================================================= + +// TestSyntheticFileName tests the syntheticFileName helper function +func TestSyntheticFileName(t *testing.T) { + testCases := []struct { + name string + sourceFile string + entityType string + entityID string + expected string + }{ + { + name: "Simple names", + sourceFile: "test.yml", + entityType: "policy", + entityID: "my-policy", + expected: "test.yml_policy_my-policy.rego", + }, + { + name: "Entity ID with colons", + sourceFile: "domain.yml", + entityType: "library", + entityID: "mrn:iam:library:utils", + expected: "domain.yml_library_mrn_iam_library_utils.rego", + }, + { + name: "Entity ID with slashes", + sourceFile: "test.yml", + entityType: "mapper", + entityID: "path/to/mapper", + expected: "test.yml_mapper_path_to_mapper.rego", + }, + { + name: "Mixed special characters", + sourceFile: "input.yml", + entityType: "policy", + entityID: "mrn:ns/policy:test", + expected: "input.yml_policy_mrn_ns_policy_test.rego", + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + result := syntheticFileName(tc.sourceFile, tc.entityType, tc.entityID) + assert.Equal(t, tc.expected, result) + }) + } +} + +// TestPerformRegalLinting_ValidFiles tests performRegalLinting with valid test data files +func TestPerformRegalLinting_ValidFiles(t *testing.T) { + ctx := context.Background() + + testCases := []struct { + name string + filename string + }{ + {"Lint valid simple", "lint-valid-simple.yml"}, + {"Alpha domain", "alpha.yml"}, + {"Consolidated domain", "consolidated.yml"}, + {"Valid alpha", "valid-alpha.yml"}, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + validFile := createTempFileFromTestData(t, tc.filename) + + // performRegalLinting should not panic or return a parse-failure error (1) + // It may return 0 (no violations) or >0 (Regal rule violations) + result := performRegalLinting(ctx, []string{validFile}) + assert.GreaterOrEqual(t, result, 0, "performRegalLinting should succeed for %s", tc.filename) + }) + } +} + +// TestPerformRegalLinting_InvalidRego tests performRegalLinting with bad Rego +func TestPerformRegalLinting_InvalidRego(t *testing.T) { + ctx := context.Background() + + badRegoFile := createTempFileFromTestData(t, "bad-rego.yml") + + result := performRegalLinting(ctx, []string{badRegoFile}) + assert.Equal(t, 1, result, "performRegalLinting should return 1 for unparseable Rego") +} + +// TestPerformRegalLinting_NoRegoContent tests performRegalLinting with a file that has no Rego +func TestPerformRegalLinting_NoRegoContent(t *testing.T) { + ctx := context.Background() + + noRegoFile := createTempFileFromTestData(t, "lint-no-rego.yml") + + result := performRegalLinting(ctx, []string{noRegoFile}) + assert.Equal(t, 0, result, "performRegalLinting should return 0 when no Rego code is found") +} + +// TestPerformRegalLinting_FileNotFound tests performRegalLinting with a non-existent file +func TestPerformRegalLinting_FileNotFound(t *testing.T) { + ctx := context.Background() + + // Non-existent files are silently skipped (parsers.Load fails, we continue) + result := performRegalLinting(ctx, []string{"/nonexistent/file.yml"}) + assert.Equal(t, 0, result, "performRegalLinting should return 0 for non-existent files (skipped)") +} + +// TestPerformRegalLinting_MultipleFiles tests performRegalLinting with multiple files +func TestPerformRegalLinting_MultipleFiles(t *testing.T) { + ctx := context.Background() + + file1 := createTempFileFromTestData(t, "lint-valid-simple.yml") + file2 := createTempFileFromTestData(t, "valid-alpha.yml") + + result := performRegalLinting(ctx, []string{file1, file2}) + assert.GreaterOrEqual(t, result, 0, "performRegalLinting should succeed for multiple valid files") +} + +// TestPerformRegalLinting_FailOpaCheck tests performRegalLinting with a file that fails OPA check +func TestPerformRegalLinting_FailOpaCheck(t *testing.T) { + ctx := context.Background() + + // fail-opa-check.yml has valid Rego syntax but uses undefined functions; + // Regal should still be able to parse and lint it (violations expected) + opaCheckFile := createTempFileFromTestData(t, "fail-opa-check.yml") + + result := performRegalLinting(ctx, []string{opaCheckFile}) + assert.GreaterOrEqual(t, result, 0, "performRegalLinting should succeed for file with OPA check errors") +} + +// TestPerformRegalLinting_MixedValidInvalid tests performRegalLinting with a mix of valid and invalid files +func TestPerformRegalLinting_MixedValidInvalid(t *testing.T) { + ctx := context.Background() + + validFile := createTempFileFromTestData(t, "valid-alpha.yml") + invalidFile := createTempFileFromTestData(t, "lint-invalid-syntax.yml") + + // Invalid YAML files are skipped by parsers.Load; valid files are linted + result := performRegalLinting(ctx, []string{validFile, invalidFile}) + assert.GreaterOrEqual(t, result, 0, "performRegalLinting should handle mix of valid and invalid files") +} diff --git a/cmd/mpe/subcommands/lint/regal.go b/cmd/mpe/subcommands/lint/regal.go new file mode 100644 index 0000000..68bbef7 --- /dev/null +++ b/cmd/mpe/subcommands/lint/regal.go @@ -0,0 +1,125 @@ +// +// Copyright © Manetu Inc. All rights reserved. +// + +package lint + +import ( + "context" + "fmt" + "strings" + + "github.com/open-policy-agent/regal/pkg/linter" + "github.com/open-policy-agent/regal/pkg/report" + "github.com/open-policy-agent/regal/pkg/rules" + + "github.com/manetu/policyengine/pkg/policydomain/parsers" +) + +// performRegalLinting runs Regal lint on all embedded Rego code extracted from the given files. +// It uses the Regal Go library directly instead of shelling out to the regal CLI. +// Returns the number of violations found. +func performRegalLinting(ctx context.Context, files []string) int { + // fileToEntityMap maps synthetic filenames to "sourceFile:entityType:entityID" + fileToEntityMap := make(map[string]string) + // regoFiles maps synthetic filenames to their Rego content + regoFiles := make(map[string]string) + + for _, file := range files { + domain, err := parsers.Load(file) + if err != nil { + continue + } + + for libID, library := range domain.PolicyLibraries { + if strings.TrimSpace(library.Rego) != "" { + syntheticName := syntheticFileName(file, "library", libID) + regoFiles[syntheticName] = library.Rego + fileToEntityMap[syntheticName] = fmt.Sprintf("%s:library:%s", file, libID) + } + } + + for policyID, policy := range domain.Policies { + if strings.TrimSpace(policy.Rego) != "" { + syntheticName := syntheticFileName(file, "policy", policyID) + regoFiles[syntheticName] = policy.Rego + fileToEntityMap[syntheticName] = fmt.Sprintf("%s:policy:%s", file, policyID) + } + } + + for i, mapper := range domain.Mappers { + if strings.TrimSpace(mapper.Rego) != "" { + mapperID := mapper.IDSpec.ID + if mapperID == "" { + mapperID = fmt.Sprintf("mapper[%d]", i) + } + syntheticName := syntheticFileName(file, "mapper", mapperID) + regoFiles[syntheticName] = mapper.Rego + fileToEntityMap[syntheticName] = fmt.Sprintf("%s:mapper:%s", file, mapperID) + } + } + } + + if len(regoFiles) == 0 { + fmt.Println("No Rego code found to lint with Regal") + return 0 + } + + return runRegalLint(ctx, regoFiles, fileToEntityMap) +} + +// syntheticFileName creates a consistent synthetic filename for a Rego entity. +func syntheticFileName(sourceFile, entityType, entityID string) string { + safeID := strings.ReplaceAll(entityID, ":", "_") + safeID = strings.ReplaceAll(safeID, "/", "_") + return fmt.Sprintf("%s_%s_%s.rego", sourceFile, entityType, safeID) +} + +// runRegalLint uses the Regal Go library to lint the provided Rego files. +func runRegalLint(ctx context.Context, regoFiles map[string]string, fileToEntityMap map[string]string) int { + input, err := rules.InputFromMap(regoFiles, nil) + if err != nil { + fmt.Printf("✗ Failed to parse Rego for Regal linting: %v\n", err) + return 1 + } + + regalLinter := linter.NewLinter().WithInputModules(&input) + + regalReport, err := regalLinter.Lint(ctx) + if err != nil { + fmt.Printf("✗ Regal linting failed: %v\n", err) + return 1 + } + + if len(regalReport.Violations) == 0 { + return 0 + } + + for _, violation := range regalReport.Violations { + entityInfo := fileToEntityMap[violation.Location.File] + printRegalViolation(violation, entityInfo) + } + + return len(regalReport.Violations) +} + +// printRegalViolation formats and prints a single Regal violation. +func printRegalViolation(violation report.Violation, entityInfo string) { + if entityInfo != "" { + parts := strings.SplitN(entityInfo, ":", 3) + if len(parts) == 3 { + file, entityType, entityID := parts[0], parts[1], parts[2] + fmt.Printf("✗ %s (Regal: %s in %s '%s' at line %d)\n", file, violation.Title, entityType, entityID, violation.Location.Row) + } else { + fmt.Printf("✗ Regal: %s at %s:%d:%d\n", violation.Title, violation.Location.File, violation.Location.Row, violation.Location.Column) + } + } else { + fmt.Printf("✗ Regal: %s at %s:%d:%d\n", violation.Title, violation.Location.File, violation.Location.Row, violation.Location.Column) + } + + fmt.Printf(" Category: %s | Level: %s\n", violation.Category, violation.Level) + if violation.Description != "" { + fmt.Printf(" Description: %s\n", violation.Description) + } + fmt.Println() +} diff --git a/cmd/mpe/test/lint-no-rego.yml b/cmd/mpe/test/lint-no-rego.yml new file mode 100644 index 0000000..86671dd --- /dev/null +++ b/cmd/mpe/test/lint-no-rego.yml @@ -0,0 +1,10 @@ +apiVersion: iamlite.manetu.io/v1alpha3 +kind: PolicyDomain +metadata: + name: no-rego-domain +spec: + roles: + - mrn: "mrn:iam:role:admin" + name: admin + policy: "mrn:iam:policy:allow-all" + diff --git a/docs/docs/reference/cli/index.md b/docs/docs/reference/cli/index.md index 1b3e06f..052b14b 100644 --- a/docs/docs/reference/cli/index.md +++ b/docs/docs/reference/cli/index.md @@ -89,6 +89,9 @@ The CLI supports a complete policy development lifecycle: # 1. Validate syntax and structure mpe lint -f domain.yaml +# 1b. Run Regal linting for Rego style and best practices +mpe lint -f domain.yaml --regal + # 2. Test individual decisions mpe test decision -b domain.yaml -i test-input.json diff --git a/docs/docs/reference/cli/lint.md b/docs/docs/reference/cli/lint.md index 76b2f4b..9539707 100644 --- a/docs/docs/reference/cli/lint.md +++ b/docs/docs/reference/cli/lint.md @@ -9,18 +9,30 @@ Validate PolicyDomain YAML files for syntax errors and lint embedded Rego code. ## Synopsis ```bash -mpe lint --file [--opa-flags ] [--no-opa-flags] +mpe lint --file [--opa-flags ] [--no-opa-flags] [--regal] ``` ## Description -The `lint` command performs comprehensive validation: +The `lint` command performs comprehensive validation of PolicyDomain YAML files. It operates in two modes: + +### Standard Mode (default) + +Runs the full validation pipeline: 1. **YAML validation**: Checks for valid YAML syntax 2. **Rego compilation**: Compiles all embedded Rego code 3. **Dependency resolution**: Validates cross-references between policies and libraries 4. **OPA check**: Runs `opa check` for additional linting +### Regal Mode (`--regal`) + +Runs [Regal](https://docs.styra.com/regal) linting **instead of** the standard validation pipeline. Regal is OPA's official linter for Rego code and checks for style issues, best practices, and potential bugs. + +- Extracts all embedded Rego from policies, policy-libraries, and mappers +- Runs Regal's full rule set against each Rego module +- No separate installation required — Regal is bundled into `mpe` + ## Options | Option | Alias | Description | Required | @@ -28,6 +40,7 @@ The `lint` command performs comprehensive validation: | `--file` | `-f` | PolicyDomain YAML file(s) to lint | Yes | | `--opa-flags` | | Additional flags for `opa check` | No | | `--no-opa-flags` | | Disable all OPA flags | No | +| `--regal` | | Run Regal linting instead of standard validation | No | ## Examples @@ -55,9 +68,21 @@ mpe lint -f my-domain.yml --opa-flags "--strict" mpe lint -f my-domain.yml --no-opa-flags ``` +### Regal Linting + +```bash +mpe lint -f my-domain.yml --regal +``` + +### Regal Linting Multiple Files + +```bash +mpe lint -f domain1.yml -f domain2.yml --regal +``` + ## Output -### Success +### Success (Standard Mode) ``` Linting YAML files... @@ -99,6 +124,26 @@ Linting YAML files... ✗ my-domain.yml (Reference error: library 'unknown-lib' not found) ``` +### Success (Regal Mode) + +``` +Running Regal linting... + +--- +Regal linting passed: 1 file(s) validated successfully +``` + +### Regal Violations + +``` +Running Regal linting... + +✗ my-domain.yml (Regal: use-assignment-operator in policy 'main' at line 12) +✗ my-domain.yml (Regal: no-whitespace-comment in library 'utils' at line 5) +--- +Regal linting completed: 2 violation(s) +``` + ## Auto-Build The lint command automatically builds `PolicyDomainReference` files before linting: @@ -119,6 +164,8 @@ Override via: ## Validation Checks +### Standard Mode + | Check | Description | |-------|-------------| | YAML syntax | Valid YAML format | @@ -129,6 +176,12 @@ Override via: | Cross-domain references | External references are valid | | OPA check | Additional OPA linting rules | +### Regal Mode + +| Check | Description | +|-------|-------------| +| Regal rules | Style, best practices, and bug detection via Regal's built-in rule set | + ## Exit Codes | Code | Description | diff --git a/go.mod b/go.mod index 115eb11..a72b60d 100644 --- a/go.mod +++ b/go.mod @@ -1,6 +1,6 @@ module github.com/manetu/policyengine -go 1.24.6 +go 1.25.0 require ( github.com/envoyproxy/go-control-plane/envoy v1.36.0 @@ -8,7 +8,8 @@ require ( github.com/labstack/echo/v4 v4.14.0 github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 github.com/oapi-codegen/runtime v1.1.2 - github.com/open-policy-agent/opa v1.12.1 + github.com/open-policy-agent/opa v1.12.2 + github.com/open-policy-agent/regal v0.38.1 github.com/pkg/errors v0.9.1 github.com/spf13/viper v1.21.0 github.com/stretchr/testify v1.11.1 @@ -21,6 +22,7 @@ require ( ) require ( + dario.cat/mergo v1.0.2 // indirect github.com/agnivade/levenshtein v1.2.1 // indirect github.com/apapsch/go-jsonmerge/v2 v2.0.0 // indirect github.com/beorn7/perks v1.0.1 // indirect @@ -36,6 +38,7 @@ require ( github.com/go-viper/mapstructure/v2 v2.4.0 // indirect github.com/gobwas/glob v0.2.3 // indirect github.com/goccy/go-json v0.10.5 // indirect + github.com/json-iterator/go v1.1.12 // indirect github.com/labstack/gommon v0.4.2 // indirect github.com/lestrrat-go/blackmagic v1.0.4 // indirect github.com/lestrrat-go/dsig v1.0.0 // indirect @@ -46,6 +49,8 @@ require ( github.com/lestrrat-go/option/v2 v2.0.0 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-isatty v0.0.20 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect github.com/pelletier/go-toml/v2 v2.2.4 // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect diff --git a/go.sum b/go.sum index 6354305..e653e38 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= +dario.cat/mergo v1.0.2/go.mod h1:E/hbnu0NxMFBjpMIE34DRGLWqDy0g5FuKDhCb31ngxA= github.com/RaveNoX/go-jsoncommentstrip v1.0.0/go.mod h1:78ihd09MekBnJnxpICcwzCMzGrKSKYe4AqU6PDYYpjk= github.com/agnivade/levenshtein v1.2.1 h1:EHBY3UOn1gwdy/VbFwgo4cxecRznFk7fKWN1KOX7eoM= github.com/agnivade/levenshtein v1.2.1/go.mod h1:QVVI16kDrtSuwcpd0p1+xMC6Z/VfhtCyDIjcwga4/DU= @@ -26,8 +28,8 @@ github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0 h1:NMZiJj8QnKe1LgsbDayM4UoHwbvw github.com/decred/dcrd/dcrec/secp256k1/v4 v4.4.0/go.mod h1:ZXNYxsqcloTdSy/rNShjYzMhyjf0LaoftYK0p+A3h40= github.com/dgraph-io/badger/v4 v4.8.0 h1:JYph1ChBijCw8SLeybvPINizbDKWZ5n/GYbz2yhN/bs= github.com/dgraph-io/badger/v4 v4.8.0/go.mod h1:U6on6e8k/RTbUWxqKR0MvugJuVmkxSNc79ap4917h4w= -github.com/dgraph-io/ristretto/v2 v2.2.0 h1:bkY3XzJcXoMuELV8F+vS8kzNgicwQFAaGINAEJdWGOM= -github.com/dgraph-io/ristretto/v2 v2.2.0/go.mod h1:RZrm63UmcBAaYWC1DotLYBmTvgkrs0+XhBd7Npn7/zI= +github.com/dgraph-io/ristretto/v2 v2.3.0 h1:qTQ38m7oIyd4GAed/QkUZyPFNMnvVWyazGXRwvOt5zk= +github.com/dgraph-io/ristretto/v2 v2.3.0/go.mod h1:gpoRV3VzrEY1a9dWAYV6T1U7YzfgttXdd/ZzL1s9OZM= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54 h1:SG7nF6SRlWhcT7cNTs5R6Hk4V2lcmLz2NsG2VnInyNo= github.com/dgryski/trifles v0.0.0-20230903005119-f50d829f2e54/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA= github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= @@ -61,14 +63,17 @@ github.com/goccy/go-json v0.10.5 h1:Fq85nIqj+gXn/S5ahsiTlK3TmC85qgirsdTP/+DeaC4= github.com/goccy/go-json v0.10.5/go.mod h1:oq7eo15ShAhp70Anwd5lgX2pLfOS3QCiwU/PULtXL6M= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= -github.com/google/flatbuffers v25.2.10+incompatible h1:F3vclr7C3HpB1k9mxCGRMXq6FdUalZ6H/pNX4FP1v0Q= -github.com/google/flatbuffers v25.2.10+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= +github.com/google/flatbuffers v25.9.23+incompatible h1:rGZKv+wOb6QPzIdkM2KxhBZCDrA0DeN6DNmRDrqIsQU= +github.com/google/flatbuffers v25.9.23+incompatible/go.mod h1:1AeVuKshWv4vARoZatz6mlQ0JxURH0Kv5+zNeJKJCa8= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2 h1:8Tjv8EJ+pM1xP8mK6egEbD1OgnVTyacbefKhmbLhIhU= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.2/go.mod h1:pkJQ2tZHJ0aFOVEEot6oZmaVEZcRme73eIFmhiVuRWs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3 h1:NmZ1PKzSTQbuGHw9DGPFomqkkLWMC+vZCkfs+FHv1Vg= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.3/go.mod h1:zQrxl1YP88HQlA6i9c63DSVPFklWpGX4OWAc9bFuaH4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= github.com/juju/gnuflag v0.0.0-20171113085948-2ce1bb71843d/go.mod h1:2PavIy+JPciBPrBUjwbNvtwB6RQlve+hkpll6QSNmOE= github.com/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co= github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0= @@ -100,14 +105,22 @@ github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWE github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/miekg/dns v1.1.57 h1:Jzi7ApEIzwEPLHWRcafCN9LZSBbqQpxjt/wpgvg7wcM= github.com/miekg/dns v1.1.57/go.mod h1:uqRjCRUuEAA6qsOiJvDd+CFo/vW+y5WR6SNmHE55hZk= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee h1:W5t00kpgFdJifH4BDsTlE89Zl93FEloxaWZfGcifgq8= +github.com/modern-go/reflect2 v1.0.3-0.20250322232337-35a7c28c31ee/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 h1:C3w9PqII01/Oq1c1nUAm88MOHcQC9l5mIlSMApZMrHA= github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= github.com/oapi-codegen/runtime v1.1.2 h1:P2+CubHq8fO4Q6fV1tqDBZHCwpVpvPg7oKiYzQgXIyI= github.com/oapi-codegen/runtime v1.1.2/go.mod h1:SK9X900oXmPWilYR5/WKPzt3Kqxn/uS/+lbpREv+eCg= -github.com/open-policy-agent/opa v1.12.1 h1:MWfmXuXB119O7rSOJ5GdKAaW15yBirjnLkFRBGy0EX0= -github.com/open-policy-agent/opa v1.12.1/go.mod h1:RnDgm04GA1RjEXJvrsG9uNT/+FyBNmozcPvA2qz60M4= +github.com/open-policy-agent/opa v1.12.2 h1:Nh60UaIBP6NKCgy45jmMHZwNYALtNR9e/hj+Lna49mc= +github.com/open-policy-agent/opa v1.12.2/go.mod h1:RnDgm04GA1RjEXJvrsG9uNT/+FyBNmozcPvA2qz60M4= +github.com/open-policy-agent/regal v0.38.1 h1:q5Qv18gg1lz95T0bmWC7d3Grn4QzJ++epuQWiIaPuhU= +github.com/open-policy-agent/regal v0.38.1/go.mod h1:rbEqDUE8sPuJaroq170bSHXavbUgcrKwlwGxIOEIleQ= github.com/pelletier/go-toml/v2 v2.2.4 h1:mye9XuhQ6gvn5h28+VilKrrPoQVanw5PMw/TB0t5Ec4= github.com/pelletier/go-toml/v2 v2.2.4/go.mod h1:2gIqNv+qfxSVS7cM2xJQKtLSTLUE9V8t9Stt+h56mCY= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -192,8 +205,8 @@ go.opentelemetry.io/otel/sdk/metric v1.39.0 h1:cXMVVFVgsIf2YL6QkRF4Urbr/aMInf+2W go.opentelemetry.io/otel/sdk/metric v1.39.0/go.mod h1:xq9HEVH7qeX69/JnwEfp6fVq5wosJsY1mt4lLfYdVew= go.opentelemetry.io/otel/trace v1.39.0 h1:2d2vfpEDmCJ5zVYz7ijaJdOF59xLomrvj7bjt6/qCJI= go.opentelemetry.io/otel/trace v1.39.0/go.mod h1:88w4/PnZSazkGzz/w84VHpQafiU4EtqqlVdxWy+rNOA= -go.opentelemetry.io/proto/otlp v1.7.1 h1:gTOMpGDb0WTBOP8JaO72iL3auEZhVmAQg4ipjOVAtj4= -go.opentelemetry.io/proto/otlp v1.7.1/go.mod h1:b2rVh6rfI/s2pHWNlB7ILJcRALpcNDzKhACevjI+ZnE= +go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= +go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -222,8 +235,8 @@ golang.org/x/tools v0.39.0 h1:ik4ho21kwuQln40uelmciQPp9SipgNDdrafrYA4TmQQ= golang.org/x/tools v0.39.0/go.mod h1:JnefbkDPyD8UU2kI5fuf8ZX4/yUeh9W877ZeBONxUqQ= gonum.org/v1/gonum v0.16.0 h1:5+ul4Swaf3ESvrOnidPp4GZbzf0mxVQpDCYUQE7OJfk= gonum.org/v1/gonum v0.16.0/go.mod h1:fef3am4MQ93R2HHpKnLk4/Tbh/s0+wqD5nfa6Pnwy4E= -google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda h1:+2XxjfsAu6vqFxwGBRcHiMaDCuZiqXGDUDVWVtrFAnE= -google.golang.org/genproto/googleapis/api v0.0.0-20251029180050-ab9386a59fda/go.mod h1:fDMmzKV90WSg1NbozdqrE64fkuTv6mlq2zxo9ad+3yo= +google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101 h1:vk5TfqZHNn0obhPIYeS+cxIFKFQgser/M2jnI+9c6MM= +google.golang.org/genproto/googleapis/api v0.0.0-20251103181224-f26f9409b101/go.mod h1:E17fc4PDhkr22dE3RgnH2hEubUaky6ZwW4VhANxyspg= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b h1:Mv8VFug0MP9e5vUxfBcE3vUkV6CImK3cMNMIDFjmzxU= google.golang.org/genproto/googleapis/rpc v0.0.0-20251222181119-0a764e51fe1b/go.mod h1:j9x/tPzZkyxcgEFkiKEEGxfvyumM01BEtsW8xzOahRQ= google.golang.org/grpc v1.78.0 h1:K1XZG/yGDJnzMdd/uZHAkVqJE+xIDOcmdSFZkBUicNc=