Skip to content

feat(agent): embedded json schema and step validation for agent go re…#287

Merged
rice-riley merged 1 commit into
mainfrom
agent-go-config-214
Jun 30, 2026
Merged

feat(agent): embedded json schema and step validation for agent go re…#287
rice-riley merged 1 commit into
mainfrom
agent-go-config-214

Conversation

@rice-riley

@rice-riley rice-riley commented Jun 25, 2026

Copy link
Copy Markdown
Member

feat(agent): config loader with embedded JSON schemas + stage/step validation (#214)

Description

Adds the config layer of the Go agent rewrite: a new internal/config package that
loads, validates, and serializes a package config.json, plus a new internal/stage
package for the lifecycle-stage vocabulary and cross-step validation rules. This is the
layer every later agent module (#215, #217, #219) builds on.

Validation follows the same split the Python agent uses: the JSON schema owns the
declarative half
(required fields, the semver/name patterns, types) and
hand-written Go owns the structural/cross-step rules (no defined steps, apply
stages without checks, UpgradeStep placement, step files existing on disk, check-pair
warnings). The v1 schemas are embedded so the binary stays a single static artifact.

Builds on #213 (internal/{schema,step,interrupts}, already on main). No changes to
the Python agent.

Architecture

The package-config domain is split into three packages, each with one job:

  • internal/stage — the lifecycle-stage vocabulary: the Stage type, the stage
    constants, ParseStage, IsStepStage, and the ApplyToCheck/CheckToApply pairing
    maps. Names are unprefixed (stage.Apply, stage.ApplyCheck) since the package name
    already namespaces them.
  • internal/step — the step types: the Step interface, RegularStep,
    UpgradeStep, Idempotence, and Decode/Encode. No dependency on stage (the
    stage → step relationship lives in config as map[stage.Stage][]step.Step).
  • internal/config — assembles the two: loads/validates/dumps the whole document
    and owns the cross-step rules that no single step can enforce.

What's in here

  • internal/config/config.goConfig struct + a Loader with Load / Dump.
    • Config holds typed Modes (map[stage.Stage][]step.Step); custom
      MarshalJSON/UnmarshalJSON route the modes section through step.Decode /
      step.Encode so the step.Step interface stays the contract.
    • Loader.Load(data, stepRootDir, *slog.Logger): read declared version →
      schema-validate (which also rejects unknown versions) → materialize modes via
      stage.ParseStage + step.Decode → run the cross-step rules.
    • Loader.Dump: emits the latest schema version; deterministic output (struct field
      order + stdlib-sorted map keys), each step re-encoded through step.Encode, and now
      runs the same structural cross-step rules Load does so a dumped config can't
      serialize cleanly yet fail on reload.
  • internal/config/validate.go — the single home for config validation. Holds both
    the SchemaValidator interface + the embedded-schema implementation
    (//go:embed schemas/v1/*.json, validated via github.com/santhosh-tekuri/jsonschema/v6)
    and the cross-step document rules (validateModes, checkModeRules,
    warnMissingCheckPairs, checkStepFilesExist).
  • internal/config/schemas/v1/*.json — copies of the Python agent's v1 schemas;
    these become the Go module's authoritative copy at cutover ([FEA]: Cutover: flip default to Go, delete Python, flatten dir, update docs #222).
  • internal/stage/stage.go — the Stage type, constants, registry (stage.All),
    pairing maps, IsStepStage, and ParseStage (which rejects unknown mode keys — the
    schema does not forbid extras).
  • internal/stepStep gained a Path() method so callers read a step's path
    through the interface instead of type-switching on the concrete types.
  • cmd/agent/main.go — establishes the agent's slog logging seam.

Closes #214.

New dependency

github.com/santhosh-tekuri/jsonschema/v6 (+ transitive github.com/dlclark/regexp2) —
the agent's first runtime dependency. Native JSON Schema Draft 2020-12 with a custom
URLLoader hook, which is what lets us resolve the cross-file $ref straight out of the
embed FS. Per AGENTS.md, flagging it explicitly as the one new pattern this PR introduces.
Rejected alternatives: pure-Go hand-rolled validation (loses the schema as the single
source of truth), google/jsonschema-go (less ergonomic embed/$ref wiring at the time),
and omissis/go-jsonschema (a code generator — it would own the type layer and collide
with the hand-built step types from #213).

Notable design decisions

Calling these out for review, since several depart from a literal port of the Python agent:

  1. Validation lives in one place (config/validate.go). Cross-step rules sit with
    Load/Dump, where the whole document is materialized — not scattered across the
    step package. This also removes the "schema" name overload: internal/schema is
    version arithmetic, and JSON-schema validation no longer has a separate schema.go.
  2. A dedicated internal/stage package. Stage is one of the three core vocab
    concepts (Status/State/Stage); giving it its own package keeps step to just the step
    types and matches how the operator treats Stage as first-class. Names are unprefixed
    (stage.Apply) since the package name carries the namespace.
  3. SchemaValidator interface + injectable Loader. Schema validation sits behind a
    small interface that Loader holds, instead of a package-level function backed by a
    global cache. The validator is stateless (the embedded schemas are tiny and
    single-version), and tests can inject a fake — no sync.Map/sync.Once machinery.
  4. No migrate function. With only v1 defined, schema validation already rejects
    unknown/newer versions, so the version check is inlined into Load rather than kept as
    a speculative migration seam. (This also drops the Python migrate() bug where it
    returns a ValidationError instead of raising it.)
  5. Stage and Step are exposed through behavior, not type switches. Step.Path()
    replaced a stepPath type switch whose default returned "" — a latent bug where a
    future step type would silently stat the root dir and pass file validation. The
    RegularStep data field is named ScriptPath so the type can expose Path().
  6. Dump enforces the cross-step contract. Dump now runs the structural rules
    (checkModeRules: empty steps, missing paired checks, misplaced UpgradeStep) before
    marshaling, so DumpLoad stays consistent. The on-disk step-file check is
    intentionally excluded — Dump records a package-relative root, not a path it can stat.
  7. Errors use sentinel values + fmt.Errorf("…: %w"), matching interrupts.go,
    instead of porting Python's StepError/ValueError hierarchy. interrupts.Decode
    returns an errors.Is-able sentinel and wraps the underlying base64/JSON error for
    diagnostics.
  8. Schema $ref resolution uses a custom URLLoader (keyed by URL basename over the
    embed FS) instead of AddResource. The v1 schemas use relative $id/$ref values
    whose URI math makes AddResource resolve to doubled-up paths; the loader keeps
    resolution entirely inside the embed FS.
  9. Logging uses slog, not controller-runtime logr. The agent is a standalone
    container entrypoint with no reconcile loop or controller-runtime dependency, so
    slog (with slog.Default() fallback when a nil logger is passed) is the right seam
    here — the operator's logr contract does not apply to this module.
  10. No byte-for-byte parity with Python's dump() output. The Go agent isn't wired
    into the operator e2e suite until cutover ([FEA]: Cutover: flip default to Go, delete Python, flatten dir, update docs #222), so dump/log string parity isn't
    load-bearing yet. Dump targets deterministic + schema-valid + round-trippable.

Testing

  • cd agent/go && make test — 5 Ginkgo suites, 110 specs, all passing.
  • cd agent/go && make lint — golangci-lint clean (0 issues).
  • cd agent/go && make build — builds with the slog seam.
  • go mod tidy is stable.

Coverage includes: valid load; missing required field; unknown schema version;
non-semver package_version; missing schema_version; DumpLoad round-trip;
Dump rejecting configs that would fail Load's cross-step rules; an injected fake
SchemaValidator (proving Load depends on the interface, not the embedded schemas);
embedded-schema compilation; every cross-step rule (no steps, only-check stages,
apply-without-check, UpgradeStep misplacement, missing step files, path-escape
rejection, and the non-fatal check-pair warning asserted via a buffer-backed
slog.Logger); expectedCheckName including dotted parent directories; stage.ParseStage
against an explicit stage list; and step.Decode defaulting (including the absent
idempotenceAuto fallback and the legacy boolean form).

Out of scope (follow-ups)

Files

 agent/go/cmd/agent/main.go                                    |   8 +
 agent/go/go.mod                                               |   1 +
 agent/go/go.sum                                               |   4 +
 agent/go/internal/config/config.go                            | 225 +++++++++++++++
 agent/go/internal/config/config_test.go                       | 195 +++++++++++++
 agent/go/internal/config/schemas/v1/skyhook-agent-schema.json | 114 ++++++++
 agent/go/internal/config/schemas/v1/step-schema.json          |  62 ++++
 agent/go/internal/config/validate.go                          | 325 ++++++++++++++++++++
 agent/go/internal/config/validate_test.go                     | 185 ++++++++++++
 agent/go/internal/interrupts/interrupts.go                    |  25 +-
 agent/go/internal/interrupts/interrupts_test.go               |  18 +-
 agent/go/internal/stage/stage.go                              | 104 +++++++
 agent/go/internal/stage/stage_test.go                         |  73 +++++
 agent/go/internal/step/regular_step.go                        |  31 +-
 agent/go/internal/step/regular_step_test.go                   |  24 +-
 agent/go/internal/step/step.go                                |  82 +-----
 agent/go/internal/step/step_test.go                           |  61 +--
 agent/go/internal/step/upgrade_step.go                        |  11 +-
 agent/go/internal/step/upgrade_step_test.go                   |  24 +-
 19 files changed, 1405 insertions(+), 167 deletions(-)

Checklist

  • I am familiar with the Contributing Guidelines.
  • My commits are signed off (git commit -s) per the DCO.
  • New or existing tests cover these changes.
  • The documentation is up to date with these changes.

@rice-riley rice-riley self-assigned this Jun 25, 2026
@rice-riley rice-riley requested a review from a team June 25, 2026 23:10
@github-actions github-actions Bot added component/operator Skyhook operator (controller-manager) component/agent Skyhook agent (package executor) component/ci CI workflows, GitHub Actions, and repo tooling labels Jun 25, 2026
@rice-riley rice-riley force-pushed the agent-go-config-214 branch from 57e56f8 to bf6263b Compare June 25, 2026 23:15
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

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

The agent now sets a default slog text logger at startup. The Go config package adds embedded JSON schemas, schema-backed load/dump logic, and cross-step validation for step modes and file paths. Internal step types now expose script paths through Path(), parse known stages through ParseStage, and use ScriptPath in RegularStep. Interrupt decoding now returns a shared sentinel error for invalid serialized payloads, and tests cover the updated behavior.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • NVIDIA/nodewright#235: Extends the same step API surface that this PR builds on, including RegularStep path handling and Step interface updates.

Suggested labels

component/tests

Suggested reviewers

  • ayuskauskas
🚥 Pre-merge checks | ✅ 3 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The core loader and validation work appears implemented, but the required Dump byte-for-byte golden test from #214 is not shown. Add a golden test that compares Dump output against the Python fixture byte-for-byte, as required by #214.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: embedded JSON schemas and step validation for the Go agent.
Out of Scope Changes check ✅ Passed No clearly unrelated changes are evident; the extra step, logging, and interrupt edits appear tied to the dependency work.
Description check ✅ Passed The description clearly matches the PR changes, covering the new config loader, embedded schemas, cross-step validation, and logging seam.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch agent-go-config-214

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

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent/go/cmd/agent/main.go`:
- Around line 26-31: The startup logging setup in main is introducing a global
slog default, which should be removed. Update main to avoid calling
slog.SetDefault and keep logging on the repo’s logr path by using
controller-runtime’s log.FromContext(ctx) / logr.Logger pattern instead of
process-wide logging state. Use the existing entrypoint in main and any
downstream logger wiring to pass an explicit logger boundary rather than relying
on hidden global defaults.

In `@agent/go/internal/config/config.go`:
- Around line 62-96: In Load, the config’s declared root_dir in cfg.RootDir is
used separately from the stepRootDir argument when calling step.Validate, which
can cause confusing path resolution drift. Add a brief guard or comment near
materialize and the step.Validate call to make the intended relationship
explicit, and if they are meant to match, assert that cfg.RootDir and
stepRootDir agree before validating steps.

In `@agent/go/internal/step/validate.go`:
- Around line 165-179: The checkStepFilesExist helper is treating every os.Stat
failure as a missing step, which masks permission or I/O problems. Update
checkStepFilesExist to distinguish os.IsNotExist(err) from other errors when
checking stepPath(s) under rootDir: only append truly missing paths to missing,
and return non-existence errors immediately so real filesystem failures are not
reported as “do not exist.”
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 2aaf549d-d134-43cf-b4db-d096c52fa0e3

📥 Commits

Reviewing files that changed from the base of the PR and between a1f3bec and 57e56f8.

⛔ Files ignored due to path filters (1)
  • agent/go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • agent/go/cmd/agent/main.go
  • agent/go/go.mod
  • agent/go/internal/config/config.go
  • agent/go/internal/config/config_test.go
  • agent/go/internal/config/schema.go
  • agent/go/internal/config/schemas/v1/skyhook-agent-schema.json
  • agent/go/internal/config/schemas/v1/step-schema.json
  • agent/go/internal/step/validate.go
  • agent/go/internal/step/validate_test.go

Comment thread agent/go/cmd/agent/main.go
Comment thread agent/go/internal/config/config.go Outdated
Comment thread agent/go/internal/step/validate.go Outdated

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent/go/internal/config/config_test.go`:
- Around line 61-71: The current schema compilation tests cover valid and
unknown versions, but they do not verify byte-for-byte parity with the Python
source schemas. Add the schema drift golden test to config_test.go alongside
compiledSchemaFor/schema.V1 coverage so the embedded schema output is compared
against the expected Python-derived golden content and will fail on any
byte-level drift.

In `@agent/go/internal/config/config.go`:
- Around line 102-139: Dump currently serializes values that Load will later
reject because it does not validate the caller-supplied stage keys or the final
wire representation. Update Dump to validate stage values in the modes map and
ensure the marshaled wireConfig is accepted by Load before returning, using the
existing Dump and Load paths as the source of truth. Focus on the Dump function,
wireConfig construction, and any stage/scheme validation helpers so bogus
step.Stage values or invalid packageVersion inputs fail early.

In `@agent/go/internal/config/schemas/v1/skyhook-agent-schema.json`:
- Around line 7-20: The v1 schema contract is missing the root_dir field even
though Load, Dump, and the test fixture treat it as part of the config shape.
Update skyhook-agent-schema.json to declare root_dir in the properties for the
schema alongside schema_version, package_name, and package_version, and make it
required so malformed configs cannot pass validation; use the existing config
handling in Load/Dump to keep the schema aligned with the serialized model.

In `@agent/go/internal/step/validate.go`:
- Around line 137-159: The missing-check warning in warnMissingCheckPairs is
matching against all check stages, which lets an unrelated check suppress a
warning for the paired step. Update the logic so each step only looks for its
corresponding check stage derived from expectedCheckName(stepPath(s)) and
compare against the matching Stage rather than the aggregated checkPaths map.
Keep the warning behavior in warnMissingCheckPairs and use stepPath,
expectedCheckName, and IsStepStage to locate the affected flow.
- Around line 90-101: The check-only guard in checkNonCheckStagePresent is too
loose because it treats an empty non-check stage slice as present. Update the
logic to verify that at least one stage in steps has a non-empty []Step, rather
than only checking for the map key. Keep the existing ApplyToCheck and error
message behavior, but make the presence check ignore empty slices so configs
like apply: [] do not bypass validation.
- Around line 165-176: `checkStepFilesExist` currently trusts
`filepath.Join(rootDir, stepPath(s))`, so absolute paths and `../` traversal can
escape the package root. Add a guard before `os.Stat` that resolves each
`stepPath(s)` to a clean relative path and rejects any entry that is absolute or
climbs outside `rootDir`, then only validate paths that stay under the root.
Keep the existing missing-file collection and error reporting, but ensure
`stepPath(s)` is validated as an in-root path before it is checked.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 7af7fa4d-118e-438f-be3d-0293b1fead93

📥 Commits

Reviewing files that changed from the base of the PR and between 57e56f8 and bf6263b.

⛔ Files ignored due to path filters (1)
  • agent/go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (9)
  • agent/go/cmd/agent/main.go
  • agent/go/go.mod
  • agent/go/internal/config/config.go
  • agent/go/internal/config/config_test.go
  • agent/go/internal/config/schema.go
  • agent/go/internal/config/schemas/v1/skyhook-agent-schema.json
  • agent/go/internal/config/schemas/v1/step-schema.json
  • agent/go/internal/step/validate.go
  • agent/go/internal/step/validate_test.go

Comment thread agent/go/internal/config/config_test.go
Comment thread agent/go/internal/config/config.go Outdated
Comment thread agent/go/internal/config/schemas/v1/skyhook-agent-schema.json
Comment thread agent/go/internal/step/validate.go Outdated
Comment thread agent/go/internal/step/validate.go Outdated
Comment thread agent/go/internal/step/validate.go Outdated

@lockwobr lockwobr left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Review

The layer is functionally correct and well-tested: the Load flow is sound, Dump determinism holds, the migrate-over-the-Python-bug fix is a real improvement, and the tests hit the genuine edge cases (round-trip, missing required field, unknown version, warn-don't-fail). Nice work on the coverage.

That said, the throughline of this review is: it reads as a transliteration of the Python agent rather than a Go rewrite. The flow, the decomposition, and several type choices are shaped to mirror skyhook_agent instead of being idiomatic Go, and the comments say so out loud ("Order mirrors Python's config.load", "Mirrors Python's Mode(mode)", "matching the Python agent's warn-don't-fail"). Behavior is expressed as package-level functions reading and mutating shared structs, plus a package-global cache, which is the procedural Python-module shape. The Go-native version pushes behavior onto types and depends on small interfaces at the seams. None of the items below are optional polish; I'd want them addressed before merge.

1. Validation is in the wrong place, and it's fragmented

Cross-step validation lives in internal/step/validate.go, but these are document-level invariants ("there must be defined steps", "apply stages must have checks", "UpgradeStep placement") — they're about the assembled config, not about the step package. Validation is currently scattered across three layers:

  • internal/config/schema.go — JSON schema validation
  • internal/step/validate.go — cross-step rules
  • the step types' own Validate() methods

So "where does a config get validated?" can't be answered from one place. Suggest moving the cross-step rules into config (where the whole document is materialized and where Load already orchestrates), leaving step to own only what's intrinsic to a step (ParseStage, Stage consts, per-type Validate()). Bonus: it resolves the naming overload between the internal/schema package (version arithmetic) and config/schema.go (actual JSON-schema validation) — two different "schema" concepts a reader has to disambiguate.

2. Lean on interfaces at the seams (the Go-native version of this design)

embeddedLoader (config/schema.go:119) is already a clean example of this done right: it satisfies jsonschema's loader interface and the compiler depends on the abstraction. Do more of that at the seams that earn it.

a. Schema validation behind a SchemaValidator interface, and kill the package global. config.validate reaches straight into santhosh-tekuri/jsonschema/v6 through a package-level schemaCache sync.Map (schema.go:59, :83). That global mutable cache is the anti-interface smell. Model it as a value that owns its cache as a field and satisfies a small interface:

type SchemaValidator interface {
    Validate(data []byte, v schema.SchemaVersion) error
}

config.Load then accepts that interface (or holds one on a Loader struct) instead of calling a package function backed by a global. The compiler+cache becomes an injectable value, tests can supply a fake instead of round-tripping the real embedded schemas, and the sync.Map/sync.Once machinery (heavier than the problem needs for compile-time-embedded files with one version today) collapses into struct state.

b. migrate should be a registry/interface if it's really "the seam future migrations slot into." It's currently a single function hardcoded to the identity transform (config.go:149), described in its own comment as a seam. The idiomatic expression of that is a Migration interface (or map[schema.SchemaVersion]Migration) you register into and range over — not a function you'll later grow a switch inside. If migrations are genuinely coming, build the seam as the type now; if not, drop the "seam" framing and just inline the version check.

c. Widen Step with Path(). The stepPath type switch (internal/step/validate.go:195) returns "" in its default case — duck-typing ported into Go, and a latent bug: a future Step type makes checkStepFilesExist call os.Stat(filepath.Join(rootDir, "")), which stats rootDir itself, succeeds, and a missing step file silently passes validation. The interface (step.go:30) only exposes Encode() and its doc comment even tells callers to type-switch. Both concrete types already carry an exported Path field, so adding Path() string deletes the switch, the silent default, and the bug. Behavior on the type, not a function switching over concrete types.

What I would not do: introduce a Validator interface for the cross-step checkX rules just to have one. Those are better as a []rule (func values) you range over — composable and individually testable without the ceremony of an interface. The test for "should this be an interface" is whether there's a real second implementation or a dependency seam to mock (schema validation: yes; migrations: yes if coming; a fixed list of internal checks: no).

3. Other Pythonic constructs to make Go-native

String-keyed "sentinel" errors built for Python text-parityinterrupts.go:28, step.go:100. Designing error messages to match Python's strings across the language boundary couples Go callers to Python's wording. If callers need to branch, use typed/sentinel errors with errors.Is. If they don't, drop the parity constraint. Either way the Go side shouldn't be pinned to Python's prose.

*bool/*string absent-vs-present probingstep.go:153, interrupts.go:65. This reproduces Python's None/KeyError distinction. Sometimes justified (genuine tri-state), but worth confirming each one needs three states rather than a zero-value default; several read as reflex ports of the Python shape.

4. Smaller idiom fixes (also should-fix)

  • Validation error precedencestep/validate.go:69 runs before :72, so a misplaced UpgradeStep with no apply-check reports the generic pairing error instead of the specific placement one. Run placement before pairing.
  • Comment density — several comments narrate the code or just record Python parity. The ones explaining a genuine surprise (json.Number handling, the json:"-" rationale on Config.Modes) stay; the parity narration should go, especially once the structure stops mirroring Python.

Nit

  • schemas/v1/step-schema.json has trailing whitespace / no trailing newline.

@rice-riley rice-riley force-pushed the agent-go-config-214 branch from bf6263b to 713be68 Compare June 26, 2026 00:50

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent/go/internal/interrupts/interrupts.go`:
- Around line 57-71: In Decode, base64 decoding and json.Unmarshal currently
drop the underlying error and return only errInvalidSerializedInterrupt, which
removes useful diagnostics. Update the Decode function to wrap the original
decode/unmarshal error while preserving the sentinel behavior so callers can
still branch with errors.Is on errInvalidSerializedInterrupt. Keep the changes
localized to the Decode path and ensure both the base64.StdEncoding.DecodeString
and json.Unmarshal failure cases retain their original error context.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: dfde1d3f-9574-46a1-bc84-4835704dd968

📥 Commits

Reviewing files that changed from the base of the PR and between bf6263b and 713be68.

⛔ Files ignored due to path filters (1)
  • agent/go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (16)
  • agent/go/cmd/agent/main.go
  • agent/go/go.mod
  • agent/go/internal/config/config.go
  • agent/go/internal/config/config_test.go
  • agent/go/internal/config/schemas/v1/skyhook-agent-schema.json
  • agent/go/internal/config/schemas/v1/step-schema.json
  • agent/go/internal/config/validate.go
  • agent/go/internal/config/validate_test.go
  • agent/go/internal/interrupts/interrupts.go
  • agent/go/internal/interrupts/interrupts_test.go
  • agent/go/internal/step/regular_step.go
  • agent/go/internal/step/regular_step_test.go
  • agent/go/internal/step/step.go
  • agent/go/internal/step/step_test.go
  • agent/go/internal/step/upgrade_step.go
  • agent/go/internal/step/upgrade_step_test.go

Comment thread agent/go/internal/interrupts/interrupts.go
@rice-riley rice-riley force-pushed the agent-go-config-214 branch from 713be68 to 45124e5 Compare June 26, 2026 01:04

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 4

♻️ Duplicate comments (2)
agent/go/internal/config/config_test.go (1)

61-71: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Please add the schema-drift golden test from the PR scope.

These specs prove the embedded schemas compile, but they still won't fail if the embedded JSON drifts byte-for-byte from the Python source schemas, which was one of the stated objectives for this work.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/config/config_test.go` around lines 61 - 71, Add the
missing schema-drift golden test to the existing schema compilation specs so the
embedded JSON is compared byte-for-byte against the Python source schemas, not
just compiled successfully. Update the "schema compilation" Describe block in
config_test.go to include a golden-style assertion using compileSchema and the
embedded schema constants (for example schema.V1), and make sure the test fails
when the embedded schema content drifts from the source fixture rather than only
on invalid JSON or version errors.
agent/go/internal/config/schemas/v1/skyhook-agent-schema.json (1)

7-20: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add root_dir to the v1 schema contract.

Config.MarshalJSON/UnmarshalJSON and the test fixtures all treat root_dir as part of the wire format, but this schema neither declares nor requires it. That lets malformed configs pass schema validation even though the serialized model includes the field.

Suggested fix
   "properties": {
     "schema_version": {
       "description": "Version of the schema",
       "type": "string"
     },
+    "root_dir": {
+      "description": "Root directory containing step files",
+      "type": "string"
+    },
     "package_name": {
       "description": "The name of the skyhook package",
       "type": "string"
     },
@@
   "required": [
     "schema_version",
+    "root_dir",
     "package_name",
     "package_version",
     "expected_config_files",
     "modes"
   ]

Also applies to: 106-112

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/config/schemas/v1/skyhook-agent-schema.json` around lines 7
- 20, The v1 schema contract is missing the root_dir field even though
Config.MarshalJSON, Config.UnmarshalJSON, and the test fixtures already treat it
as part of the wire format. Update the schema definition in
skyhook-agent-schema.json to declare root_dir alongside schema_version,
package_name, and package_version, and make it a required property so malformed
configs are rejected during validation.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent/go/internal/config/config.go`:
- Around line 21-24: The config loading and validation flow still depends on
*slog.Logger and falls back to slog.Default(), so update the API to use
context.Context and logr instead. Thread the reconcile context through Load and
validateModes, and switch logging to log.FromContext(ctx) at the call sites in
config.go and validate.go. Remove the logger parameter/defaulting pattern and
ensure the existing load/validation paths get the context from their callers.

In `@agent/go/internal/config/validate.go`:
- Around line 299-308: expectedCheckName is deriving the suffix from the full
path, so dots in parent directories produce incorrect check names and false
warnings. Update expectedCheckName to compute the name from the basename only
(for example by separating directory and base first, then applying the “_check”
suffix to the base), and keep warnMissingCheckPairs using the corrected result
so paths like dir.v1/foo map to dir.v1/foo_check.

In `@agent/go/internal/step/step_test.go`:
- Around line 121-133: The ParseStage spec currently depends on the Stages
registry, so it only checks that ParseStage and Stages agree instead of
verifying the full expected set. Update the ParseStage test in step_test.go to
assert against an explicit list of known stage values rather than ranging over
Stages, using ParseStage and the existing stage constants/types to validate each
expected stage. Keep the unknown-stage assertion as-is so registry drift is
caught if a new Stage is added but not registered.

In `@agent/go/internal/step/step.go`:
- Around line 56-71: The stage registry is currently exposed as the exported
slice Stages, which lets other packages mutate the runtime validation set used
by ParseStage. Make the registry immutable by hiding the underlying list behind
an unexported variable or by returning a copied slice from an accessor, and
update any uses of Stages in step.go to go through the safe immutable path so
accepted wire keys cannot be changed at runtime.

---

Duplicate comments:
In `@agent/go/internal/config/config_test.go`:
- Around line 61-71: Add the missing schema-drift golden test to the existing
schema compilation specs so the embedded JSON is compared byte-for-byte against
the Python source schemas, not just compiled successfully. Update the "schema
compilation" Describe block in config_test.go to include a golden-style
assertion using compileSchema and the embedded schema constants (for example
schema.V1), and make sure the test fails when the embedded schema content drifts
from the source fixture rather than only on invalid JSON or version errors.

In `@agent/go/internal/config/schemas/v1/skyhook-agent-schema.json`:
- Around line 7-20: The v1 schema contract is missing the root_dir field even
though Config.MarshalJSON, Config.UnmarshalJSON, and the test fixtures already
treat it as part of the wire format. Update the schema definition in
skyhook-agent-schema.json to declare root_dir alongside schema_version,
package_name, and package_version, and make it a required property so malformed
configs are rejected during validation.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: 088678d9-90b0-4de9-a791-0da0149a3f51

📥 Commits

Reviewing files that changed from the base of the PR and between 713be68 and 45124e5.

⛔ Files ignored due to path filters (1)
  • agent/go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (16)
  • agent/go/cmd/agent/main.go
  • agent/go/go.mod
  • agent/go/internal/config/config.go
  • agent/go/internal/config/config_test.go
  • agent/go/internal/config/schemas/v1/skyhook-agent-schema.json
  • agent/go/internal/config/schemas/v1/step-schema.json
  • agent/go/internal/config/validate.go
  • agent/go/internal/config/validate_test.go
  • agent/go/internal/interrupts/interrupts.go
  • agent/go/internal/interrupts/interrupts_test.go
  • agent/go/internal/step/regular_step.go
  • agent/go/internal/step/regular_step_test.go
  • agent/go/internal/step/step.go
  • agent/go/internal/step/step_test.go
  • agent/go/internal/step/upgrade_step.go
  • agent/go/internal/step/upgrade_step_test.go

Comment thread agent/go/internal/config/config.go
Comment thread agent/go/internal/config/validate.go Outdated
Comment thread agent/go/internal/step/step_test.go Outdated
Comment thread agent/go/internal/step/step.go Outdated
@rice-riley rice-riley force-pushed the agent-go-config-214 branch from 45124e5 to cb84d95 Compare June 26, 2026 01:34

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
agent/go/internal/step/step_test.go (1)

146-159: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Make the defaulting test actually omit idempotence.

Line 147 still sends idempotence:false, so this spec doesn't exercise the absent-field fallback to Auto; it only proves the legacy bool form decodes. Removing the field here, or splitting out a separate legacy-bool case, will catch regressions in the real defaulting path.

Suggested change
-		data := []byte(`{"path":"foo.sh","idempotence":false,"upgrade_step":false}`)
+		data := []byte(`{"path":"foo.sh","upgrade_step":false}`)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/step/step_test.go` around lines 146 - 159, The defaulting
spec in Decode is still including the legacy idempotence field, so it does not
verify the missing-field fallback. Update the “applies defaults to a minimal
payload” test in step_test.go to omit idempotence entirely when constructing the
JSON passed to Decode, and keep the assertion that RegularStep.Idempotence
defaults to Auto. If you still want coverage for the legacy boolean form, add a
separate test case for that Decode behavior.
♻️ Duplicate comments (3)
agent/go/internal/config/config_test.go (1)

61-71: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Add the schema-drift golden test from the PR scope.

These assertions only prove the embedded schemas compile. The PR objective also calls for drift protection against the Python source schemas, and nothing here would fail if the embedded JSON diverged while still compiling. Based on PR objectives, the work should include schema drift protection via a golden test.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/config/config_test.go` around lines 61 - 71, The current
schema compilation tests in compileSchema only verify embedded schema validity
and do not protect against drift from the Python source schemas. Add the
schema-drift golden test from the PR scope alongside the existing
Describe("schema compilation") coverage so it compares the generated embedded
schema output against the Python source schema artifacts. Use the existing
compileSchema and schema.V1/schema.SchemaVersion symbols as the anchor, and
ensure the new test fails when embedded JSON diverges even if it still compiles.
agent/go/internal/config/schemas/v1/skyhook-agent-schema.json (1)

7-20: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Add root_dir to the v1 schema contract.

agent/go/internal/config/config.go serializes root_dir, and agent/go/internal/config/config_test.go treats it as part of the required wire shape, but this schema neither declares nor requires it. That lets malformed configs pass schema validation while leaving Config.RootDir unset after Load.

Suggested fix
   "properties": {
     "schema_version": {
       "description": "Version of the schema",
       "type": "string"
     },
+    "root_dir": {
+      "description": "Root directory containing step files",
+      "type": "string"
+    },
     "package_name": {
       "description": "The name of the skyhook package",
       "type": "string"
     },
@@
   "required": [
     "schema_version",
+    "root_dir",
     "package_name",
     "package_version",
     "expected_config_files",
     "modes"
   ]

Also applies to: 106-112

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/config/schemas/v1/skyhook-agent-schema.json` around lines 7
- 20, The v1 schema contract is missing the root_dir field, so configs can
validate without populating Config.RootDir. Update the schema object in
skyhook-agent-schema.json to declare root_dir alongside the existing properties,
and mark it as required so the contract matches what config.go serializes and
config_test.go expects. Keep the change aligned with the schema’s existing
property naming and validation style.
agent/go/internal/config/config.go (1)

21-24: 📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

Use context.Context/logr here instead of plumbing *slog.Logger.

Loader.Load now bakes *slog.Logger into the config API, and validateModes/warnMissingCheckPairs continue the same contract. That conflicts with the repo logging guideline and makes this layer harder to compose with the rest of the Go code. As per coding guidelines, Use controller-runtime's logr.Logger obtained via log.FromContext(ctx) for logging; do not introduce slog or stdlib log and Plumb the reconcile ctx through instead of creating context.Background() inside the reconcile path.

#!/bin/bash
set -euo pipefail
rg -n 'log/slog|\*slog\.Logger|slog\.Default\(\)' agent/go/internal/config agent/go/cmd/agent

Also applies to: 135-145

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agent/go/internal/config/config.go` around lines 21 - 24, `Loader.Load`,
`validateModes`, and `warnMissingCheckPairs` are still tied to `*slog.Logger`,
which violates the repo logging contract and makes the config package harder to
compose. Refactor these APIs to accept `context.Context` and pull a
controller-runtime `logr.Logger` via `log.FromContext(ctx)` at the call sites,
then thread the reconcile context through instead of introducing
`context.Background()` or any `slog` dependency in this path.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agent/go/internal/config/config.go`:
- Around line 167-199: Dump currently only validates schema and misses the
cross-step checks that Load performs, so configs can serialize successfully and
still fail on reload. Update Loader.Dump to run the same validateModes logic
used by Load before marshaling, using the same inputs from modes and
expectedConfigFiles so cases like empty steps, missing paired check/apply
stages, or misplaced UpgradeStep are rejected up front. Keep the fix localized
to Dump and reference the existing Load/validateModes behavior to ensure the
Dump→Load contract stays consistent.

---

Outside diff comments:
In `@agent/go/internal/step/step_test.go`:
- Around line 146-159: The defaulting spec in Decode is still including the
legacy idempotence field, so it does not verify the missing-field fallback.
Update the “applies defaults to a minimal payload” test in step_test.go to omit
idempotence entirely when constructing the JSON passed to Decode, and keep the
assertion that RegularStep.Idempotence defaults to Auto. If you still want
coverage for the legacy boolean form, add a separate test case for that Decode
behavior.

---

Duplicate comments:
In `@agent/go/internal/config/config_test.go`:
- Around line 61-71: The current schema compilation tests in compileSchema only
verify embedded schema validity and do not protect against drift from the Python
source schemas. Add the schema-drift golden test from the PR scope alongside the
existing Describe("schema compilation") coverage so it compares the generated
embedded schema output against the Python source schema artifacts. Use the
existing compileSchema and schema.V1/schema.SchemaVersion symbols as the anchor,
and ensure the new test fails when embedded JSON diverges even if it still
compiles.

In `@agent/go/internal/config/config.go`:
- Around line 21-24: `Loader.Load`, `validateModes`, and `warnMissingCheckPairs`
are still tied to `*slog.Logger`, which violates the repo logging contract and
makes the config package harder to compose. Refactor these APIs to accept
`context.Context` and pull a controller-runtime `logr.Logger` via
`log.FromContext(ctx)` at the call sites, then thread the reconcile context
through instead of introducing `context.Background()` or any `slog` dependency
in this path.

In `@agent/go/internal/config/schemas/v1/skyhook-agent-schema.json`:
- Around line 7-20: The v1 schema contract is missing the root_dir field, so
configs can validate without populating Config.RootDir. Update the schema object
in skyhook-agent-schema.json to declare root_dir alongside the existing
properties, and mark it as required so the contract matches what config.go
serializes and config_test.go expects. Keep the change aligned with the schema’s
existing property naming and validation style.
🪄 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: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: ed115967-74cc-4791-b256-ff9523c32517

📥 Commits

Reviewing files that changed from the base of the PR and between 45124e5 and cb84d95.

⛔ Files ignored due to path filters (1)
  • agent/go/go.sum is excluded by !**/*.sum
📒 Files selected for processing (16)
  • agent/go/cmd/agent/main.go
  • agent/go/go.mod
  • agent/go/internal/config/config.go
  • agent/go/internal/config/config_test.go
  • agent/go/internal/config/schemas/v1/skyhook-agent-schema.json
  • agent/go/internal/config/schemas/v1/step-schema.json
  • agent/go/internal/config/validate.go
  • agent/go/internal/config/validate_test.go
  • agent/go/internal/interrupts/interrupts.go
  • agent/go/internal/interrupts/interrupts_test.go
  • agent/go/internal/step/regular_step.go
  • agent/go/internal/step/regular_step_test.go
  • agent/go/internal/step/step.go
  • agent/go/internal/step/step_test.go
  • agent/go/internal/step/upgrade_step.go
  • agent/go/internal/step/upgrade_step_test.go

Comment thread agent/go/internal/config/config.go
@rice-riley rice-riley force-pushed the agent-go-config-214 branch from cb84d95 to 6b8634d Compare June 26, 2026 16:33
Comment thread agent/go/internal/interrupts/interrupts.go Outdated
Comment thread agent/go/internal/step/step.go Outdated
@rice-riley rice-riley force-pushed the agent-go-config-214 branch from 6b8634d to b602830 Compare June 30, 2026 18:49
@rice-riley rice-riley force-pushed the agent-go-config-214 branch from bff9310 to 68c2c5c Compare June 30, 2026 18:53
@rice-riley rice-riley merged commit 2e18083 into main Jun 30, 2026
11 checks passed
@rice-riley rice-riley deleted the agent-go-config-214 branch June 30, 2026 19:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

component/agent Skyhook agent (package executor) component/ci CI workflows, GitHub Actions, and repo tooling component/operator Skyhook operator (controller-manager)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[FEA]: Config loader with embedded JSON schemas + Steps.validate

3 participants