Skip to content

feat: environment values and named environments (#53)#127

Merged
michaelmcnees merged 12 commits intomainfrom
feat/environment-values
Apr 7, 2026
Merged

feat: environment values and named environments (#53)#127
michaelmcnees merged 12 commits intomainfrom
feat/environment-values

Conversation

@michaelmcnees
Copy link
Copy Markdown
Collaborator

@michaelmcnees michaelmcnees commented Apr 6, 2026

Summary

  • Add values file support (--values) for supplying input and env overrides from YAML files, similar to Helm --values or Terraform .tfvars
  • Add named stored environments (--env) backed by Postgres, enabling mantle run workflow --env production without a file on disk
  • Add mantle env create/list/show/delete CLI subcommands for environment CRUD
  • Enforce 4-layer override precedence: workflow defaults < named env < values file < --input flags
  • CEL env variable respects the same layering: config < values/env < MANTLE_ENV_*

New Commands

mantle env create production --from prod.values.yaml
mantle env list
mantle env show production
mantle env delete staging
mantle run my-workflow --values prod.values.yaml
mantle run my-workflow --env production
mantle run my-workflow --env staging --values override.yaml --input url=https://custom.com

Test plan

  • Unit tests for LoadValues, LoadValuesBytes (8 cases: valid, missing file, bad YAML, unknown keys, inputs-only, env-only, uppercase normalization, empty)
  • Unit tests for MergeInputs (7 cases including 4-layer precedence)
  • Unit tests for CEL SetValuesEnv (3 cases: layering, OS precedence, nil clears)
  • Integration tests for EnvironmentStore CRUD against real Postgres via testcontainers (6 cases: create+get, not found, list, delete, duplicate name, invalid name)
  • go vet ./... clean
  • go build ./cmd/mantle clean
  • CI passes

Closes #53

🤖 Generated with Claude Code


Note

Medium Risk
Adds a new Postgres-backed environments table plus new CLI surfaces and changes how mantle run assembles inputs/env, which can affect execution behavior and requires migration correctness.

Overview
Adds values-file overrides and persistent named environments for parameterizing workflow execution.

mantle run now supports --values (load YAML inputs/env) and --env (fetch stored overrides from Postgres), and merges inputs with precedence named env < values file < inline --input; plan adds flags to validate a values file or environment exists.

Introduces mantle env create/list/show/delete for CRUD of named environments, backed by a new environments table/migration and environment.Store implementation, and updates CEL env resolution to include an intermediate “values/env overrides” layer between config and MANTLE_ENV_* OS variables (with new tests covering precedence and parsing/validation).

Reviewed by Cursor Bugbot for commit 767999a. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • New Features

    • Persistent named environments with DB migration and CLI commands to create/list/show/delete them.
    • plan/run accept --values and --env to merge values files and named environments into runtime inputs.
    • CEL environment layering updated to: config → values file → OS env, with OS env taking highest precedence.
  • Documentation

    • Added docs for health checks, credentials handling, DB context helpers, and metrics helpers.
  • Tests

    • New unit and integration tests covering values loading, input merging, CEL precedence, and environment store CRUD.

michaelmcnees and others added 7 commits April 6, 2026 00:08
Implements Task 1-3 for issue #53 (environment variables and promotion).
Adds values file parsing with unknown-key validation and env key
normalization, plus a three-layer MergeInputs helper (defaults <
values file < inline flags).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Inserts a values-file precedence layer between config env and MANTLE_ENV_*
OS variables (config < values < MANTLE_ENV_*), exposed via SetValuesEnv.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add --values <file> flag to both `mantle run` and `mantle plan`.
The run command merges values file inputs (lower priority than --input
flags) and applies env overrides via CEL.SetValuesEnv. The plan command
validates the values file early for fast failure feedback.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Adds migration 018 creating the environments table with team-scoped
unique name constraint, and implements Store with Create/Get/List/Delete
backed by testcontainers integration tests (all 5 passing).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Implements `mantle env` (create/list/show/delete) backed by the
environment.Store, and adds --env to `mantle run` (4-source input merge:
named env < values file < --input flags) and `mantle plan` (existence
validation only).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…fy plan help text

- Extend MergeInputs to 4 layers (defaults < env < values < inline) and update
  all call sites in values_test.go; add TestMergeInputs_FourLayerPrecedence
- Refactor run.go to use workflow.MergeInputs instead of manual merge loops
- Add environment name validation (^[a-z][a-z0-9-]*$) in Store.Create with test
- Clarify plan --values and --env flag descriptions to note they do not affect diff

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds values-file parsing and input/env merging, a DB-backed named environment store with CLI management, wires --values/--env into run/plan to merge inputs, and extends the CEL Evaluator with SetValuesEnv to layer env as config → values → OS MANTLE_ENV_*.

Changes

Cohort / File(s) Summary
CEL Environment Layering
packages/engine/internal/cel/cel.go, packages/engine/internal/cel/cel_test.go, packages/engine/internal/cel/cel_values_test.go
Added (*Evaluator).SetValuesEnv(map[string]string), changed mergeEnvVars signature to accept a valuesEnv map, adjusted env merge precedence to configEnvvaluesEnv → OS MANTLE_ENV_*, emit info when OS overrides, and updated/added tests for layering, OS override, and clearing the values layer.
Workflow Values & Input Merge
packages/engine/internal/workflow/values.go, packages/engine/internal/workflow/values_test.go
New workflow.Values type (Inputs, Env), LoadValues/LoadValuesBytes with validation, uppercase-normalization and collision detection for env keys, and MergeInputs(...) implementing precedence: workflow defaults → named env inputs → values-file inputs → inline --input. Unit tests added.
Environment Store & DB Migration
packages/engine/internal/db/migrations/018_environments.sql, packages/engine/internal/environment/store.go, packages/engine/internal/environment/store_test.go
Added environments migration and Postgres-backed environment.Store with Create, Get, List, Delete. Enforces name validation, team scoping, uniqueness; includes integration tests (Testcontainers) for CRUD, ordering, duplicates, and validation.
CLI: env management & integration
packages/engine/internal/cli/env.go, packages/engine/internal/cli/root.go, packages/engine/internal/cli/plan.go, packages/engine/internal/cli/run.go
New mantle env command group (create --from, list, show, delete) and newEnvStore helper. run and plan accept --values and --env; run merges inputs via MergeInputs, resolves named envs, and applies combined values/env to the CEL evaluator using SetValuesEnv, passing merged inputs to execution.
DB & Context helpers, docs, metrics, credentials docs
packages/engine/internal/db/db.go, packages/engine/internal/api/health/health.go, packages/engine/internal/cli/credentials.go, packages/engine/internal/metrics/metrics.go
Added context helpers for passing *sql.DB, updated GoDoc for health handlers and credentials helpers (Default/Save/Load/Delete), and added documentation comments for metrics helpers (no behavior changes).

Sequence Diagram

sequenceDiagram
    participant CLI as CLI (mantle)
    participant WF as Workflow (LoadValues / MergeInputs)
    participant DB as Database (environments)
    participant Eng as Engine (Executor)
    participant CEL as CEL (Evaluator)

    CLI->>WF: LoadValues(--values)?
    WF-->>CLI: Values{Inputs,Env}
    CLI->>DB: Get(--env name)?
    DB-->>CLI: Environment{Inputs,Env}
    CLI->>WF: MergeInputs(defaults, envInputs, valuesInputs, inlineInputs)
    WF-->>CLI: merged inputs
    CLI->>Eng: ExecuteWithOptions(merged inputs)
    Eng->>CEL: SetConfigEnv(config env)
    Eng->>CEL: SetValuesEnv(values + named env overlay)
    Note right of CEL: OS MANTLE_ENV_* are also read (highest precedence)
    Eng->>CEL: Eval(step expressions using env.<KEY>)
    CEL-->>Eng: evaluated results
    Eng-->>CLI: execution complete
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Possibly related PRs

Poem

🐰
I hopped through files and stitched a seam,
Values layered like a dream,
Named envs tucked in rows so neat,
CLI hums and workflows meet,
The rabbit cheers — deploy's complete.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 57.41% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: environment values and named environments' clearly and concisely summarizes the main changes: adding YAML values-file support and named stored environments with CLI management.
Linked Issues check ✅ Passed All objectives from #53 are met: values-file loader with LoadValues/LoadValuesBytes, named DB-persisted environments with CLI CRUD, --values/--env wiring to run/plan/apply, correct layering (workflow < env < values < inline), validation/error handling, and comprehensive unit/integration tests.
Out of Scope Changes check ✅ Passed All changes align with PR objectives: new values/environment packages, CLI commands (env create/list/show/delete, plan/run updates), CEL SetValuesEnv, database migration, and supporting tests. Minor documentation additions (health, credentials, db, metrics) are reasonable supporting cleanups.

✏️ 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 feat/environment-values

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

Copy link
Copy Markdown
Contributor

@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: 1

🧹 Nitpick comments (3)
packages/engine/internal/workflow/values.go (1)

47-54: Potential silent data loss during env key normalization.

When normalizing env keys to uppercase, if the input contains both api_key and API_KEY, one value will silently overwrite the other. Consider detecting and warning about (or rejecting) conflicting keys.

🔧 Proposed fix to detect key collisions
 	// Normalize env keys to uppercase (matches config.go behavior).
 	if len(vals.Env) > 0 {
 		normalized := make(map[string]string, len(vals.Env))
 		for k, v := range vals.Env {
-			normalized[strings.ToUpper(k)] = v
+			upper := strings.ToUpper(k)
+			if existing, ok := normalized[upper]; ok && existing != v {
+				return nil, fmt.Errorf("duplicate env key after normalization: %q and another key both normalize to %q", k, upper)
+			}
+			normalized[upper] = v
 		}
 		vals.Env = normalized
 	}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/engine/internal/workflow/values.go` around lines 47 - 54,
Normalizing env keys with strings.ToUpper into vals.Env can silently overwrite
values when both lowercase and uppercase variants exist (e.g., "api_key" and
"API_KEY"); update the normalization in values.go to detect collisions by
checking if normalized key already exists in the new map before assigning, and
handle conflicts by either returning an error or emitting a clear warning
(include both conflicting original keys and their values in the message) so the
caller can resolve duplicates instead of losing data; reference the vals.Env
variable, the normalized map creation, and strings.ToUpper usage when
implementing the check.
packages/engine/internal/cli/run.go (1)

79-89: Consider early values-file validation for consistency with plan.go.

In plan.go, the values file is validated before opening the database connection (fail-fast). Here, it's loaded after the DB and environment resolution. For consistency and to avoid unnecessary DB connections when the values file is invalid, consider moving this block before db.Open.

♻️ Suggested reordering
+			// Load values file early so we fail fast on bad input.
+			var valuesInputs map[string]any
+			var valuesEnv map[string]string
+			if valuesFile != "" {
+				vals, valErr := workflow.LoadValues(valuesFile)
+				if valErr != nil {
+					return fmt.Errorf("loading values file: %w", valErr)
+				}
+				valuesInputs = vals.Inputs
+				valuesEnv = vals.Env
+			}
+
 			database, err := db.Open(cfg.Database)
 			if err != nil {
 				return fmt.Errorf("failed to connect to database: %w", err)
 			}
 			defer database.Close()

Then remove the duplicate block at lines 79-89.

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

In `@packages/engine/internal/cli/run.go` around lines 79 - 89, The values-file
loading block (using valuesFile and workflow.LoadValues) should be moved earlier
to validate the values file before opening the database (db.Open) so we
fail-fast like plan.go; relocate the existing valuesFile handling to precede the
DB/env resolution and then remove the duplicate block in run.go (the vals,
valErr -> valuesInputs/valuesEnv section) to avoid redundant loading and
unnecessary DB connections.
packages/engine/internal/cli/env.go (1)

117-129: Consider sorting keys for deterministic output.

Map iteration order in Go is non-deterministic. For better UX and testability, consider sorting the keys before printing.

♻️ Suggested fix for deterministic output
+import "sort"

 			if len(env.Inputs) > 0 {
 				fmt.Fprintln(cmd.OutOrStdout(), "\nInputs:")
+				inputKeys := make([]string, 0, len(env.Inputs))
+				for k := range env.Inputs {
+					inputKeys = append(inputKeys, k)
+				}
+				sort.Strings(inputKeys)
-				for k, v := range env.Inputs {
+				for _, k := range inputKeys {
+					v := env.Inputs[k]
 					fmt.Fprintf(cmd.OutOrStdout(), "  %s: %v\n", k, v)
 				}
 			}
 			if len(env.Env) > 0 {
 				fmt.Fprintln(cmd.OutOrStdout(), "\nEnv:")
+				envKeys := make([]string, 0, len(env.Env))
+				for k := range env.Env {
+					envKeys = append(envKeys, k)
+				}
+				sort.Strings(envKeys)
-				for k, v := range env.Env {
+				for _, k := range envKeys {
+					v := env.Env[k]
 					fmt.Fprintf(cmd.OutOrStdout(), "  %s: %s\n", k, v)
 				}
 			}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/engine/internal/cli/env.go` around lines 117 - 129, The output loops
over env.Inputs and env.Env (in the printing code using fmt.Fprintf and
fmt.Fprintln) rely on Go map iteration which is non-deterministic; to make
output deterministic and testable, collect the map keys for env.Inputs and
env.Env into slices, sort them with sort.Strings, and then iterate the sorted
key slices when printing instead of ranging directly over the maps (preserve the
existing formatting and use the same fmt.Fprintf calls inside the sorted loops).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/engine/internal/cli/env.go`:
- Around line 135-155: The delete command currently calls store.Delete(...) but
does not emit an audit event; update the Store to accept an AuditEmitter
dependency (add an AuditEmitter field on the Store struct and wire it in via
newEnvStore), then after a successful call to Store.Delete(ctx, name) inside
newEnvDeleteCommand call the emitter (e.g., store.AuditEmitter.Emit or
EmitEvent) with the command context, the operation name ("environment.delete"),
target resource identifier (args[0]) and any relevant metadata (actor,
timestamp, outcome) so every state-changing delete emits an audit event; ensure
nil-checking of store.AuditEmitter so the code remains safe if not provided.

---

Nitpick comments:
In `@packages/engine/internal/cli/env.go`:
- Around line 117-129: The output loops over env.Inputs and env.Env (in the
printing code using fmt.Fprintf and fmt.Fprintln) rely on Go map iteration which
is non-deterministic; to make output deterministic and testable, collect the map
keys for env.Inputs and env.Env into slices, sort them with sort.Strings, and
then iterate the sorted key slices when printing instead of ranging directly
over the maps (preserve the existing formatting and use the same fmt.Fprintf
calls inside the sorted loops).

In `@packages/engine/internal/cli/run.go`:
- Around line 79-89: The values-file loading block (using valuesFile and
workflow.LoadValues) should be moved earlier to validate the values file before
opening the database (db.Open) so we fail-fast like plan.go; relocate the
existing valuesFile handling to precede the DB/env resolution and then remove
the duplicate block in run.go (the vals, valErr -> valuesInputs/valuesEnv
section) to avoid redundant loading and unnecessary DB connections.

In `@packages/engine/internal/workflow/values.go`:
- Around line 47-54: Normalizing env keys with strings.ToUpper into vals.Env can
silently overwrite values when both lowercase and uppercase variants exist
(e.g., "api_key" and "API_KEY"); update the normalization in values.go to detect
collisions by checking if normalized key already exists in the new map before
assigning, and handle conflicts by either returning an error or emitting a clear
warning (include both conflicting original keys and their values in the message)
so the caller can resolve duplicates instead of losing data; reference the
vals.Env variable, the normalized map creation, and strings.ToUpper usage when
implementing the check.
🪄 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: a58e3b56-3b00-456f-a22c-8781d87e8ef4

📥 Commits

Reviewing files that changed from the base of the PR and between 80d4c14 and 8eab6cf.

⛔ Files ignored due to path filters (1)
  • go.work.sum is excluded by !**/*.sum
📒 Files selected for processing (12)
  • packages/engine/internal/cel/cel.go
  • packages/engine/internal/cel/cel_test.go
  • packages/engine/internal/cel/cel_values_test.go
  • packages/engine/internal/cli/env.go
  • packages/engine/internal/cli/plan.go
  • packages/engine/internal/cli/root.go
  • packages/engine/internal/cli/run.go
  • packages/engine/internal/db/migrations/018_environments.sql
  • packages/engine/internal/environment/store.go
  • packages/engine/internal/environment/store_test.go
  • packages/engine/internal/workflow/values.go
  • packages/engine/internal/workflow/values_test.go

Comment on lines +135 to +155
func newEnvDeleteCommand() *cobra.Command {
return &cobra.Command{
Use: "delete <name>",
Short: "Delete an environment",
Args: cobra.ExactArgs(1),
RunE: func(cmd *cobra.Command, args []string) error {
store, cleanup, err := newEnvStore(cmd)
if err != nil {
return err
}
defer cleanup()

if err := store.Delete(cmd.Context(), args[0]); err != nil {
return err
}

fmt.Fprintf(cmd.OutOrStdout(), "Deleted environment %q\n", args[0])
return nil
},
}
}
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot Apr 6, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if environment.Store.Delete emits audit events
rg -n -A 15 'func.*Delete.*context' packages/engine/internal/environment/store.go | head -40

Repository: dvflw/mantle

Length of output: 554


🏁 Script executed:

cat -n packages/engine/internal/environment/store.go | sed -n '125,160p'

Repository: dvflw/mantle

Length of output: 709


🏁 Script executed:

rg -n -B 5 -A 10 'Audit.*Delete|EmitAudit.*Delete' packages/engine/internal/environment/

Repository: dvflw/mantle

Length of output: 38


🏁 Script executed:

rg -n -B 2 -A 20 'func.*Store.*Create.*ctx context' packages/engine/internal/environment/store.go | head -50

Repository: dvflw/mantle

Length of output: 843


🏁 Script executed:

rg -n -B 5 -A 15 'type Store struct' packages/engine/internal/environment/store.go

Repository: dvflw/mantle

Length of output: 750


🏁 Script executed:

rg -n -A 35 'func.*Store.*Create.*ctx context' packages/engine/internal/environment/store.go | head -70

Repository: dvflw/mantle

Length of output: 1245


🏁 Script executed:

rg -n 'AuditEmitter|EmitAudit' packages/engine/internal/environment/store.go

Repository: dvflw/mantle

Length of output: 38


🏁 Script executed:

rg -n 'type AuditEmitter|interface.*AuditEmitter' packages/engine/internal/

Repository: dvflw/mantle

Length of output: 38


🏁 Script executed:

rg -n 'AuditEmitter' packages/engine/internal/ | head -20

Repository: dvflw/mantle

Length of output: 38


🏁 Script executed:

rg -r 'AuditEmitter' packages/ --type go

Repository: dvflw/mantle

Length of output: 38


🏁 Script executed:

cat -n CLAUDE.md | head -50

Repository: dvflw/mantle

Length of output: 2632


Emit audit event for delete operation.

The Delete method performs a state-changing operation but does not emit an audit event. Per the architecture principle ("Audit from day one — every state-changing operation emits an audit event via the AuditEmitter interface"), this operation must emit an audit event via the AuditEmitter interface. The Store struct will need an AuditEmitter dependency injected to support this.

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

In `@packages/engine/internal/cli/env.go` around lines 135 - 155, The delete
command currently calls store.Delete(...) but does not emit an audit event;
update the Store to accept an AuditEmitter dependency (add an AuditEmitter field
on the Store struct and wire it in via newEnvStore), then after a successful
call to Store.Delete(ctx, name) inside newEnvDeleteCommand call the emitter
(e.g., store.AuditEmitter.Emit or EmitEvent) with the command context, the
operation name ("environment.delete"), target resource identifier (args[0]) and
any relevant metadata (actor, timestamp, outcome) so every state-changing delete
emits an audit event; ensure nil-checking of store.AuditEmitter so the code
remains safe if not provided.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Pushing back on this one. secret.Store — the closest analog — does not have an AuditEmitter. Audit events in the CLI layer are emitted by commands that modify execution state (cancel.go, rotate_key.go), not by CRUD stores. Adding an AuditEmitter to environment.Store alone would be inconsistent.

If we want audit coverage for all CRUD operations (secrets, environments, etc.), that should be a separate cross-cutting concern. Happy to file an issue for it.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@michaelmcnees, that's a fair point — applying audit emission only to environment.Store while secret.Store and other CRUD stores are unadjusted would be inconsistent, and the existing pattern of emitting audit events in execution-state commands (not stores) is a reasonable architectural boundary.

If you'd like, I can open a GitHub issue to track cross-cutting audit coverage for all CRUD stores (secrets, environments, etc.) as a follow-up. Just say the word.


✏️ Learnings added
Learnt from: michaelmcnees
Repo: dvflw/mantle PR: 127
File: packages/engine/internal/cli/env.go:146-166
Timestamp: 2026-04-06T04:47:39.490Z
Learning: In the Mantle project, audit events are emitted at the CLI command layer (e.g., `cancel.go`, `rotate_key.go`) only for execution-state-changing operations, not inside CRUD stores (e.g., `secret.Store`, `environment.Store`). Do not flag missing audit event emission in CRUD store methods; that is a separate cross-cutting concern planned for a future milestone.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.


🧠 Learnings used
Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T05:02:25.695Z
Learning: Applies to packages/engine/cmd/**/*.go : Use Cobra for CLI framework implementation.

Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T05:02:25.695Z
Learning: Applies to packages/engine/**/*.go : Configuration should use mantle.yaml with CLI flag and env var overrides.

Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T04:56:56.455Z
Learning: Applies to packages/engine/internal/**/*.go : Every state-changing operation must emit an audit event via the AuditEmitter interface.

Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T05:02:25.695Z
Learning: Applies to packages/engine/**/*.go : Every state-changing operation must emit an audit event via the AuditEmitter interface.

Learnt from: michaelmcnees
Repo: dvflw/mantle PR: 0
File: :0-0
Timestamp: 2026-03-25T12:58:26.665Z
Learning: In the Mantle project, the audit emitter is a `NoopEmitter` in V1. Rollback audit events (e.g., `ActionArtifactDeleted`) are intentionally deferred to V1.1 when the Postgres-backed auditor is implemented. Do not flag missing rollback audit events as a review issue until then.

Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T05:02:25.695Z
Learning: Applies to packages/engine/**/*.go : Engine development happens in `packages/engine/`. Run `go test ./...` and `go build ./cmd/mantle` from that directory.

Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T04:56:56.455Z
Learning: Applies to packages/engine/**/*.go : Engine development happens in `packages/engine/`. Run `go test ./...` and `go build ./cmd/mantle` from that directory.

Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T04:56:56.455Z
Learning: Applies to mantle.yaml : Use mantle.yaml for configuration with CLI flag and env var overrides.

Learnt from: michaelmcnees
Repo: dvflw/mantle PR: 45
File: internal/connector/email_receive.go:126-135
Timestamp: 2026-03-25T19:44:56.827Z
Learning: When reviewing Go code that uses go-imap v2 `github.com/emersion/go-imap/v2/imapclient.Client.Store(numSet, store, options)`, do not require a separate `UIDStore` method call. The method decides between `UID STORE` and plain `STORE` at runtime based on `numSet`’s concrete type: passing an `imap.UIDSet` should be treated as correct and will send `UID STORE`, while `imap.SeqSet` will send plain `STORE`. Only flag `client.Store` usage if `numSet` is of an unexpected type or the call signature is otherwise incorrect.

- Sort map keys in `env show` output for deterministic display
- Move values file loading before DB open in run.go (fail-fast)
- Detect env key collisions during uppercase normalization in values.go

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@michaelmcnees
Copy link
Copy Markdown
Collaborator Author

Review feedback addressed in f2943f6

Nitpick: sorted map output in env show — Fixed. Keys for both Inputs and Env maps are now collected, sorted with sort.Strings, and iterated in order.

Nitpick: fail-fast values loading in run.go — Fixed. --input flag parsing and --values file loading now happen before db.Open, matching plan.go's pattern.

Nitpick: env key collision detection in values.go — Fixed. LoadValuesBytes now returns an error like env key collision: "api_key" and "API_KEY" both normalize to "API_KEY" when different-cased keys would silently merge during uppercase normalization. Added test.

Inline: audit events on env delete — Pushed back (see thread). secret.Store doesn't emit audit events either. This should be a cross-cutting concern across all CRUD stores, not added to environment.Store in isolation.

Copy link
Copy Markdown
Contributor

@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 (1)
packages/engine/internal/workflow/values_test.go (1)

205-233: Consider renaming test to reflect actual coverage.

This test is named FullPrecedence but passes nil for envInputs, testing only 3 of the 4 layers. The actual 4-layer test is TestMergeInputs_FourLayerPrecedence below.

📝 Suggested rename
-func TestMergeInputs_FullPrecedence(t *testing.T) {
+func TestMergeInputs_ThreeLayerPrecedence(t *testing.T) {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/engine/internal/workflow/values_test.go` around lines 205 - 233, The
test TestMergeInputs_FullPrecedence is misnamed because it passes nil for
envInputs and therefore only verifies three layers; either rename it to reflect
three-layer coverage (e.g., TestMergeInputs_ThreeLayerPrecedence) or modify the
test to include a non-nil envInputs map and assertions to verify that envInputs
fit into the precedence chain; update the test name or add an envInputs variable
and corresponding expectation checks so the test's behavior matches its name,
referencing MergeInputs, TestMergeInputs_FullPrecedence and
TestMergeInputs_FourLayerPrecedence to keep naming consistent with the existing
four-layer test.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/engine/internal/workflow/values_test.go`:
- Around line 205-233: The test TestMergeInputs_FullPrecedence is misnamed
because it passes nil for envInputs and therefore only verifies three layers;
either rename it to reflect three-layer coverage (e.g.,
TestMergeInputs_ThreeLayerPrecedence) or modify the test to include a non-nil
envInputs map and assertions to verify that envInputs fit into the precedence
chain; update the test name or add an envInputs variable and corresponding
expectation checks so the test's behavior matches its name, referencing
MergeInputs, TestMergeInputs_FullPrecedence and
TestMergeInputs_FourLayerPrecedence to keep naming consistent with the existing
four-layer test.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 7075aff7-e804-4a86-9a10-332f5ed7f604

📥 Commits

Reviewing files that changed from the base of the PR and between 8eab6cf and f2943f6.

📒 Files selected for processing (4)
  • packages/engine/internal/cli/env.go
  • packages/engine/internal/cli/run.go
  • packages/engine/internal/workflow/values.go
  • packages/engine/internal/workflow/values_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • packages/engine/internal/workflow/values.go
  • packages/engine/internal/cli/env.go

The test passes nil for envInputs, exercising only 3 of the 4 merge
layers. Rename to accurately reflect actual coverage; the full 4-layer
test already exists as TestMergeInputs_FourLayerPrecedence.

https://claude.ai/code/session_01ScMbDdk1f4m7HGyjfTrmv1
Copy link
Copy Markdown
Collaborator Author

@coderabbitai Addressed your feedback — renamed TestMergeInputs_FullPrecedence to TestMergeInputs_ThreeLayerPrecedence (commit 767999a) since the test passes nil for envInputs and only exercises 3 of the 4 merge layers. The actual 4-layer test is TestMergeInputs_FourLayerPrecedence. Please re-review.


Generated by Claude Code

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check current test names and their bodies in values_test.go
rg -n "func Test" packages/engine/internal/workflow/values_test.go

echo "---"
# Show the ThreeLayerPrecedence test body
rg -n -A 25 "TestMergeInputs_ThreeLayerPrecedence" packages/engine/internal/workflow/values_test.go

echo "---"
# Show the FourLayerPrecedence test body
rg -n -A 30 "TestMergeInputs_FourLayerPrecedence" packages/engine/internal/workflow/values_test.go

Length of output: 3095


@michaelmcnees The rename looks great. TestMergeInputs_ThreeLayerPrecedence accurately reflects that it passes nil for envInputs and exercises defaults → values → inline. TestMergeInputs_FourLayerPrecedence correctly populates all four layers and validates each precedence step:

  • url: inline wins over values, env, and defaults ✅
  • retries: values (10) wins over env (5) and defaults (3) ✅
  • region: env (eu-west-1) wins over defaults (us-west-2) ✅

The test naming is now precise and the coverage is complete. LGTM!


🧠 Learnings used
Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T04:56:56.455Z
Learning: Applies to mantle.yaml : Use mantle.yaml for configuration with CLI flag and env var overrides.

Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T05:02:25.695Z
Learning: Applies to packages/engine/**/*.go : Configuration should use mantle.yaml with CLI flag and env var overrides.

Resolves CodeRabbit docstring coverage warning on PR #127. All six
functions in cli/env.go now have Go doc comments, bringing coverage
from ~17% to 100% for that file.

https://claude.ai/code/session_01E2yrXXhJW7Jt7ZPt7JYMWF
@cursor
Copy link
Copy Markdown

cursor bot commented Apr 6, 2026

You have used all of your free Bugbot PR reviews.

To receive reviews on all of your PRs, visit the Cursor dashboard to activate Pro and start your 14-day free trial.

Copy link
Copy Markdown
Contributor

@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 (1)
packages/engine/internal/cli/env.go (1)

96-101: Consider adding timezone indicator to timestamp output.

The timestamp format "2006-01-02 15:04:05" doesn't indicate the timezone. Users in different timezones viewing the same environment may find this confusing.

🔧 Optional: Add UTC suffix or use RFC3339
 			for _, e := range envs {
-				fmt.Fprintf(w, "%s\t%s\n", e.Name, e.CreatedAt.Format("2006-01-02 15:04:05"))
+				fmt.Fprintf(w, "%s\t%s\n", e.Name, e.CreatedAt.UTC().Format("2006-01-02 15:04:05 UTC"))
 			}

Alternatively, use time.RFC3339 for an unambiguous ISO format.

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

In `@packages/engine/internal/cli/env.go` around lines 96 - 101, The printed
timestamps in the env listing (inside the loop that calls fmt.Fprintf with
e.CreatedAt.Format("2006-01-02 15:04:05")) lack timezone information; update
that formatting to include timezone or use an unambiguous RFC3339 format (e.g.,
use time.RFC3339 or append the timezone with "MST"/offset) and ensure you
convert to the desired zone (e.g., e.CreatedAt.UTC() if you want UTC) before
formatting so the output in fmt.Fprintf shows the timezone/offset.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/engine/internal/cli/env.go`:
- Around line 96-101: The printed timestamps in the env listing (inside the loop
that calls fmt.Fprintf with e.CreatedAt.Format("2006-01-02 15:04:05")) lack
timezone information; update that formatting to include timezone or use an
unambiguous RFC3339 format (e.g., use time.RFC3339 or append the timezone with
"MST"/offset) and ensure you convert to the desired zone (e.g.,
e.CreatedAt.UTC() if you want UTC) before formatting so the output in
fmt.Fprintf shows the timezone/offset.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: b8dbf3bd-e4ba-48ae-87f4-5d77606978c6

📥 Commits

Reviewing files that changed from the base of the PR and between 767999a and e00dfd4.

📒 Files selected for processing (1)
  • packages/engine/internal/cli/env.go

- Add UTC indicator to env list timestamp output
- Add missing godoc comments to credentials, health, db, and metrics packages

https://claude.ai/code/session_01KiE3FJpfF6XE2y3Tyth2r3
Copy link
Copy Markdown
Collaborator Author

@coderabbitai The latest commit (e37f3fc) addresses your remaining feedback:

  • Timezone indicator: env list now formats timestamps as 2006-01-02 15:04:05 UTC using .UTC() before formatting.
  • Docstring coverage: Added missing godoc comments to internal/api/health, internal/cli/credentials, internal/db, and internal/metrics packages.

Please re-review when you get a chance.


Generated by Claude Code

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# 1. Check timestamp formatting in env.go
rg -n "UTC\|Format\|CreatedAt" packages/engine/internal/cli/env.go

echo "=== env.go full list section ==="
rg -n -A 5 -B 2 "CreatedAt" packages/engine/internal/cli/env.go

echo "=== api/health package doc ==="
fd -e go . packages/engine/internal/api/health --exec head -5 {}

echo "=== cli/credentials package doc ==="
fd -e go . packages/engine/internal/cli/credentials --exec head -5 {}
# fallback: look for credentials.go directly
rg -n "^// Package\|^package " packages/engine/internal/cli/ -g "*.go" | grep -i "cred\|Package"

echo "=== db package doc ==="
fd -e go . packages/engine/internal/db --exec head -5 {}

echo "=== metrics package doc ==="
fd -e go . packages/engine/internal/metrics --exec head -5 {}

echo "=== Check all package-level doc comments ==="
rg -n "^// Package" packages/engine/internal/api/health/ packages/engine/internal/db/ packages/engine/internal/metrics/ 2>/dev/null || true
rg -rn "^// Package" packages/engine/internal/ -g "*.go" | grep -E "health|credentials|db|metrics"

Length of output: 910


🏁 Script executed:

#!/bin/bash
echo "=== api/health - all .go files first 8 lines ==="
fd -e go . packages/engine/internal/api/health --exec sh -c 'echo "FILE: {}"; head -8 {}'

echo ""
echo "=== db - doc.go or package-level files ==="
fd -e go . packages/engine/internal/db --max-depth 1 --exec sh -c 'echo "FILE: {}"; head -8 {}'

echo ""
echo "=== metrics - all .go files first 8 lines ==="
fd -e go . packages/engine/internal/metrics --exec sh -c 'echo "FILE: {}"; head -8 {}'

echo ""
echo "=== credentials in cli ==="
fd "credentials" packages/engine/internal/cli --exec sh -c 'echo "FILE: {}"; head -8 {}'

echo ""
echo "=== Any doc.go files ==="
fd "doc.go" packages/engine/internal/

Length of output: 1546


@michaelmcnees The timezone fix looks great — e.CreatedAt.UTC().Format("2006-01-02 15:04:05 UTC") is confirmed at line 99 of env.go. ✅

However, after checking the four packages mentioned for docstring coverage, the // Package doc comments don't appear to be present yet. All four files still start with a bare package declaration:

File Status
packages/engine/internal/api/health/health.go package health — no doc comment
packages/engine/internal/db/db.go package db — no doc comment
packages/engine/internal/metrics/metrics.go package metrics — no doc comment
packages/engine/internal/cli/credentials.go package cli — no doc comment

No doc.go files were found either. In Go, a package-level doc comment must appear immediately before the package declaration, e.g.:

// Package health provides HTTP handlers for liveness and readiness probes.
package health

Could you double-check that commit e37f3fc was pushed and the branch is up to date? If the comments were added, they may not have been pushed yet. Once those are in place, everything else looks good and this PR is ready to go! 🚀


🧠 Learnings used
Learnt from: michaelmcnees
Repo: dvflw/mantle PR: 0
File: :0-0
Timestamp: 2026-03-28T01:37:57.692Z
Learning: When reviewing a PR for the dvflw/mantle repository, if all issues are resolved and the PR is ready for release, mark it as approved using the [approve] action tag, as requested by michaelmcnees.

Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T04:56:56.455Z
Learning: Applies to mantle.yaml : Use mantle.yaml for configuration with CLI flag and env var overrides.

Copy link
Copy Markdown
Contributor

@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: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/engine/internal/cli/env.go`:
- Around line 77-79: The env list command currently allows positional arguments;
update the cobra.Command instantiation for the "list" command (the struct with
Use: "list", Short: "List all environments", RunE: ...) to set Args to
cobra.NoArgs so the command rejects any extra positional arguments; locate the
anonymous command object and add the Args: cobra.NoArgs field alongside
Use/Short/RunE to enforce zero args.
🪄 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: 35f62fbc-72ff-4e74-badd-c4274cbb36b7

📥 Commits

Reviewing files that changed from the base of the PR and between e00dfd4 and e37f3fc.

📒 Files selected for processing (5)
  • packages/engine/internal/api/health/health.go
  • packages/engine/internal/cli/credentials.go
  • packages/engine/internal/cli/env.go
  • packages/engine/internal/db/db.go
  • packages/engine/internal/metrics/metrics.go
✅ Files skipped from review due to trivial changes (4)
  • packages/engine/internal/api/health/health.go
  • packages/engine/internal/db/db.go
  • packages/engine/internal/cli/credentials.go
  • packages/engine/internal/metrics/metrics.go

- Add cobra.NoArgs to env list command to reject accidental extra args
- Add package doc comments to health, db, metrics, and cli packages

https://claude.ai/code/session_01Kt2cz2tqC28r9b6bM1c7LE
Copy link
Copy Markdown
Collaborator Author

@coderabbitai the remaining feedback from your last review has been addressed in commit e3b03fc:

  • Added Args: cobra.NoArgs to the env list command (packages/engine/internal/cli/env.go) to reject accidental extra arguments.
  • Added // Package ... godoc comments to internal/api/health/health.go, internal/db/db.go, internal/metrics/metrics.go, and internal/cli/credentials.go to bring docstring coverage above the 80% threshold.

Please re-review when you get a chance.


Generated by Claude Code

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 6, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== env list command - check for cobra.NoArgs ==="
rg -n "NoArgs\|list\|Args" packages/engine/internal/cli/env.go | head -30

echo ""
echo "=== api/health/health.go - first 6 lines ==="
fd "health.go" packages/engine/internal/api/health --exec sh -c 'echo "FILE: {}"; head -6 {}'

echo ""
echo "=== db/db.go - first 6 lines ==="
fd "db.go" packages/engine/internal/db --max-depth 1 --exec sh -c 'echo "FILE: {}"; head -6 {}'

echo ""
echo "=== metrics/metrics.go - first 6 lines ==="
fd "metrics.go" packages/engine/internal/metrics --exec sh -c 'echo "FILE: {}"; head -6 {}'

echo ""
echo "=== credentials.go - first 6 lines ==="
fd "credentials.go" packages/engine/internal/cli --exec sh -c 'echo "FILE: {}"; head -6 {}'

Length of output: 877


🏁 Script executed:

#!/bin/bash
echo "=== env list command block in env.go ==="
rg -n -A 20 "newEnvListCommand" packages/engine/internal/cli/env.go | head -40

Length of output: 1354


@michaelmcnees Everything is confirmed in commit e3b03fc:

  • cobra.NoArgs is present at line 79 of env.go for the env list command ✅
  • // Package ... godoc comments are confirmed in all four files:
    • health.go: // Package health provides HTTP health-check endpoints for the Mantle API server.
    • db.go: // Package db provides Postgres connection management and query helpers.
    • metrics.go: // Package metrics defines Prometheus metrics for the Mantle server.
    • credentials.go: // Package cli implements the Mantle command-line interface.

All review feedback has been addressed. This PR looks great — approving! 🚀

[approve]


🧠 Learnings used
Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T05:02:25.695Z
Learning: Applies to packages/engine/**/*.go : Configuration should use mantle.yaml with CLI flag and env var overrides.

Learnt from: michaelmcnees
Repo: dvflw/mantle PR: 0
File: :0-0
Timestamp: 2026-03-28T01:37:57.692Z
Learning: When reviewing a PR for the dvflw/mantle repository, if all issues are resolved and the PR is ready for release, mark it as approved using the [approve] action tag, as requested by michaelmcnees.

Learnt from: CR
Repo: dvflw/mantle PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-27T04:56:56.455Z
Learning: Applies to mantle.yaml : Use mantle.yaml for configuration with CLI flag and env var overrides.

@michaelmcnees michaelmcnees merged commit 8b652eb into main Apr 7, 2026
7 checks passed
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.

[Feature] Environment variables and promotion (dev/staging/prod)

2 participants