Skip to content

feat(agent): port enums and steps for agent go rewrite#235

Merged
rice-riley merged 3 commits into
mainfrom
port-enums-and-steps
Jun 9, 2026
Merged

feat(agent): port enums and steps for agent go rewrite#235
rice-riley merged 3 commits into
mainfrom
port-enums-and-steps

Conversation

@rice-riley

Copy link
Copy Markdown
Member

Description

Ports the Step and SchemaVersion data types from Python (agent/skyhook-agent/) to two new Go packages under agent/go/internal/, building on the interrupts port that landed earlier. Wire format and lifecycle semantics are preserved so Go and Python agents agree on the bytes.

internal/step/ mirrors skyhook_agent/step.py:

  • Step is a Go interface with eight getters plus an Encode() ([]byte, error) method. Two implementations satisfy it: RegularStep (the default; Python's plain Step) and UpgradeStep (Python's class UpgradeStep(Step)), which embeds RegularStep and enforces Python's "no arguments" invariant via its own Validate.
  • Both implementations are constructed via functional options: NewRegularStep(path, opts...) / NewUpgradeStep(path, opts...) with WithName, WithArguments, WithReturncodes, WithEnv, WithOnHost, WithIdempotence, WithRequiresInterrupt. Defaults (Name from Path, Arguments=[], Returncodes=[0], Env={}, OnHost=true, Idempotence=Auto) are applied in one place via applyDefaults.
  • Decode(data []byte) (Step, error) reads the JSON wire payload and returns either a RegularStep or UpgradeStep based on the upgrade_step discriminator, propagating the Python-parity UpgradeStep invariant error.
  • Encode lives on the interface, so dispatch is by Go type: RegularStep.Encode emits upgrade_step:false, UpgradeStep.Encode emits upgrade_step:true after validating its invariant. A pinning test guards the embedding override.
  • Also ports Mode (11 values, including Interrupt, PostInterrupt, etc.), the ApplyToCheck / CheckToApply maps, the nonStepModes set, IsStepMode, and the Idempotence enum with Validate.

internal/schema/ ports SchemaVersion from skyhook_agent/enums.py:

  • SchemaVersion typed string with V1 and Latest constants. Latest sorts above any concrete version, matching Python's SortableEnum.
  • Compare, Highest, ParseSchemaVersion, and LatestFrom helpers replace Python's operator-overload comparison and get_latest_schema.

Deliberate divergences from Python are documented at the divergence site:

  • step.Idempotence.Validate uses "X is not a valid idempotence value" (Go is more specific than Python's "X is not a valid mode").
  • step.Decode is intentionally looser than Python's Step.load: Python requires idempotence in the payload; Go treats it as optional and defaults to Auto.
  • step.RegularStep.RequiresInterrupt is populated by Decode but intentionally dropped by Encode, mirroring Python's Step.dump which also omits it.

Out of scope (continues the "pure data types" boundary): the agent runtime (controller.py, chroot_exec.py), the cluster-aware Steps validator, and the JSON-schema validation pipeline from config.py.

Closes #213

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 May 14, 2026
@coderabbitai

coderabbitai Bot commented May 14, 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

Adds a schema package with SchemaVersion type, ParseSchemaVersion, Compare, Highest, LatestFrom, and tests; and a step package defining Step/Stage/Idempotence, RegularStep with functional options/defaulting/Encode, UpgradeStep embedding with a no-arguments invariant and encoder, Decode dispatch that selects concrete step types from JSON, and Ginkgo/Gomega tests covering defaults, validation, decoding, and round-trip serialization.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Suggested reviewers

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

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR implements most core requirements from #213: internal/step and internal/schema packages with parity tests, functional options, Encode/Decode with discriminators, and proper defaults—but does not complete the full scope of #213 including CI workflow setup, Makefile targets, .dockerignore, or internal/interrupts port. Verify that this PR is scoped only to the data-type implementations (step and schema) and that CI/Makefile/interrupts are addressed in separate PRs, or confirm that those items were deferred by design.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: porting enums and steps to the Go agent rewrite.
Description check ✅ Passed The description comprehensively explains the PR changes, ports, design choices, and deliberate divergences from Python.
Out of Scope Changes check ✅ Passed All changes are directly aligned with porting Step and SchemaVersion: four Go files implementing core types/logic and five test files validating behavior. No unrelated additions like CI/Makefile/interrupts appear in this PR.

✏️ 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 port-enums-and-steps

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

@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/schema/schema.go`:
- Around line 93-110: The comparator currently discards parse errors from
parseVersionNumber and always returns -1/1, and ParseSchemaVersion returns
non‑canonical numeric inputs; update both to preserve canonical values and
consistent ordering: in the comparator (where
parseVersionNumber(a)/parseVersionNumber(b) are called) check and
propagate/handle parse errors instead of ignoring them—if either parse fails,
fall back to a deterministic lexical comparison of the SchemaVersion strings; in
ParseSchemaVersion call parseVersionNumber(normalized) and, when it succeeds,
convert the numeric result to the canonical SchemaVersion string (use a small
helper like formatSchemaVersion or similar) and return that canonical value, and
when parseVersionNumber fails return the parse error (don’t swallow it). Ensure
you reference and use parseVersionNumber and ParseSchemaVersion (and add
formatSchemaVersion if needed) so serialized values and comparator behavior stay
consistent.
🪄 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: fa64f032-24fc-48df-bab5-9e8373b25c00

📥 Commits

Reviewing files that changed from the base of the PR and between 8597c67 and 65b8e63.

📒 Files selected for processing (10)
  • agent/go/internal/schema/schema.go
  • agent/go/internal/schema/schema_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
  • agent/go/internal/step/wire.go
  • agent/go/internal/step/wire_test.go

Comment thread agent/go/internal/schema/schema.go Outdated
Comment thread agent/go/internal/step/regular_step.go Outdated
Comment thread agent/go/internal/step/step.go
@ayuskauskas

Copy link
Copy Markdown
Collaborator

Zero-value RegularStep{} literal silently breaks wire parity for on_host

In Python, Step(path="foo.sh") defaults on_host=True and emits "on_host":true. In Go, RegularStep{path: "foo.sh"}.Encode() emits "on_host":false because applyDefaults deliberately doesn't touch onHost (Go bools have no absent state). This is documented at regular_step.go:317-320, and the test at regular_step_test.go:404-408 asserts the behavior.

The implicit contract is "always construct via NewRegularStep," which is fine in principle — but several tests in this PR construct RegularStep{…} literals directly (regular_step_test.go:369, 419, wire_test.go:1148, 1188, 1204) and pass only because they happen to set onHost: true (or don't check it). A future caller — or someone copying the test patterns — will silently produce non-Python-parity output.

Two ways to address it, either is fine:

  • (a) Make RegularStep unexported (rename to regularStep) so construction can only happen through NewRegularStep. The Step interface stays exported, so callers still get everything they need.
  • (b) Use *bool for onHost internally (or a sentinel like Idempotence does via empty-string) so applyDefaults can distinguish "not set" from "explicit false" and default to true. This also makes the encode side symmetric with the decode side, which already uses *bool.

If you'd rather keep the current design, at minimum I'd add a golden-bytes test for NewRegularStep("foo.sh").Encode() that asserts the exact Python-equivalent bytes ("on_host":true), so the constructor path is pinned with the same rigor the literal-construction path currently has.

@ayuskauskas

Copy link
Copy Markdown
Collaborator

schema.Compare swallows parse errors and mis-orders parse-equal versions

schema.go:88-105:

aNum, _ := parseVersionNumber(a)
bNum, _ := parseVersionNumber(b)
if aNum < bNum {
    return -1
}
return 1

Two issues here:

  • Parse errors are dropped. If a is "v1" and b is "vfoo", bNum is 0 and the function silently returns 1. ParseSchemaVersion is the gate, not Compare — anyone calling Compare on a raw SchemaVersion("…") (which the type allows, since it's just a string alias) gets the silent path.
  • The fallthrough is return 1, not return 0. Two versions that string-differ but parse-equal (e.g., "v1" vs "v01") return 1 instead of 0. Today nothing produces "v01", but ParseSchemaVersion lowercases and trims and doesn't strip leading zeros, so it's reachable.

The numeric-equal fix is one line — add if aNum == bNum { return 0 } between the two ifs. For the parse-error path, either return an (int, error) or panic — by the time anyone is calling Compare on a value, an unparseable input is a programming bug, and silently treating it as 0 will hide it.

Worth adding tests for both cases (parse-equal-but-string-different, and an invalid input) — the latter will fail today, which is exactly the point.

@github-actions

github-actions Bot commented Jun 6, 2026

Copy link
Copy Markdown

@rice-riley this PR has been inactive for 21 days. Do you need help finishing it, or should we close it for now? Feel free to reopen anytime.

@rice-riley rice-riley force-pushed the port-enums-and-steps branch from 65b8e63 to b38b809 Compare June 9, 2026 00:15
@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 9, 2026

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

♻️ Duplicate comments (1)
agent/go/internal/schema/schema.go (1)

126-136: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Canonicalize parsed versions to v{number} format and validate version numbers are >= 1.

ParseSchemaVersion currently returns the normalized input without re-formatting the numeric portion. Inputs like "v01" return as "v01" instead of the canonical "v1", allowing non-canonical representations to propagate into storage, serialization, and cross-system contracts. This breaks the implicit assumption that SchemaVersion values are in canonical v{N} form and can cause downstream inconsistencies.

Additionally, the function does not validate that version numbers are >= 1, so invalid versions like "v0" or "v-1" would be accepted.

Capture the parsed number, validate it is >= 1, and return the canonical SchemaVersion(fmt.Sprintf("v%d", n)) representation.

🔧 Proposed fix
 func ParseSchemaVersion(value string) (SchemaVersion, error) {
 	normalized := SchemaVersion(strings.ToLower(strings.TrimSpace(value)))
 	if normalized == Latest {
 		return normalized, nil
 	}
-	if _, err := parseVersionNumber(normalized); err != nil {
+	n, err := parseVersionNumber(normalized)
+	if err != nil {
 		return "", fmt.Errorf("invalid schema version %q: %w", value, err)
 	}
-	return normalized, nil
+	if n < 1 {
+		return "", fmt.Errorf("invalid schema version %q: must be >= v1", value)
+	}
+	return SchemaVersion(fmt.Sprintf("v%d", n)), nil
 }
🤖 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/schema/schema.go` around lines 126 - 136,
ParseSchemaVersion currently returns the lowercased/trimmed input (e.g., "v01")
and doesn't enforce version >= 1; update it to call
parseVersionNumber(normalized) to obtain the integer n, validate n >= 1, and
then return the canonical SchemaVersion using fmt.Sprintf("v%d", n) (instead of
returning normalized). Keep error wrapping the same when parseVersionNumber
fails and return an explicit error if n < 1. Ensure references:
ParseSchemaVersion, parseVersionNumber, SchemaVersion, and Latest are used as
described.
🤖 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/schema/schema_test.go`:
- Around line 80-83: Add two new test cases in schema_test.go targeting
ParseSchemaVersion: one that verifies canonicalization (call ParseSchemaVersion
with "v01" and "V002", assert no error and that returned value equals
SchemaVersion("v1") and SchemaVersion("v2") respectively) and another that
verifies rejection of non-positive versions (call ParseSchemaVersion with "v0"
and "v-1" and assert an error is returned; for "v0" also assert the error
message contains text like "must be >= v1"). Use the existing test style (It
blocks and Expect assertions) so they integrate with the current tests for
ParseSchemaVersion.

In `@agent/go/internal/step/regular_step.go`:
- Around line 54-59: The Encode method in RegularStep should wrap returned
errors with context instead of returning bare errors: when calling
s.Idempotence.Validate() (in RegularStep.Encode) wrap any returned error using
fmt.Errorf with a descriptive prefix like "validate idempotence: %w", and when
json.Marshal(s) fails wrap that error similarly (e.g. "marshal RegularStep:
%w"); keep the existing s.applyDefaults() call and ensure you import fmt if not
already imported.
- Around line 54-60: The Encode method on RegularStep must ensure the
discriminator is fixed: after calling s.applyDefaults() and validating
Idempotence (in RegularStep.Encode), set s.UpgradeStep = false unconditionally
before calling json.Marshal(s) so the marshalled payload cannot be coerced into
an upgrade step by a caller that previously set the exported field.

In `@agent/go/internal/step/step.go`:
- Around line 169-170: The call to u.Validate() inside Decode returns the bare
err (return nil, err) which breaks the repo's error-wrapping rule; update Decode
so that when u.Validate() fails you wrap the error with context (e.g., using
fmt.Errorf("upgrade step validation failed: %w", err)) before returning, and add
the fmt import if needed; locate the u.Validate() call in the Decode function in
step.go and replace the bare return with a wrapped error that references the
validation failure.

In `@agent/go/internal/step/upgrade_step_test.go`:
- Around line 103-143: Add a new unit case that constructs an UpgradeStep value
directly (not via NewUpgradeStep) and calls UpgradeStep.Encode() to verify the
wire contains the discriminator `"upgrade_step":true`; specifically, create an
UpgradeStep instance (using the UpgradeStep struct with RegularStep fields set
appropriately), call its Encode method and Expect the dumped bytes to
ContainSubstring(`"upgrade_step":true`), placing the test alongside the existing
Describe("UpgradeStep.Encode", ...) cases to pin ownership of the discriminator
independent of NewUpgradeStep.

In `@agent/go/internal/step/upgrade_step.go`:
- Around line 44-45: The call to u.Validate() in NewUpgradeStep returns a bare
err; update NewUpgradeStep to wrap that validation error with context using
fmt.Errorf and %w (e.g., include "NewUpgradeStep" or "upgrade step validation"
in the message) before returning so callers get call-site context; ensure fmt is
imported if not already.
- Around line 69-74: UpgradeStep.Encode currently just validates and delegates
to RegularStep.Encode, allowing a directly-constructed UpgradeStep to serialize
with the discriminator "upgrade_step": false; update UpgradeStep.Encode to
ensure the discriminator is forced to true before encoding by setting the
UpgradeStep-specific discriminator flag on the embedded RegularStep (or
otherwise set the field used by RegularStep.Encode that controls "upgrade_step")
and then call Validate and RegularStep.Encode (i.e. in UpgradeStep.Encode, set
the discriminator to true, call u.Validate(), then return
u.RegularStep.Encode()).

---

Duplicate comments:
In `@agent/go/internal/schema/schema.go`:
- Around line 126-136: ParseSchemaVersion currently returns the
lowercased/trimmed input (e.g., "v01") and doesn't enforce version >= 1; update
it to call parseVersionNumber(normalized) to obtain the integer n, validate n >=
1, and then return the canonical SchemaVersion using fmt.Sprintf("v%d", n)
(instead of returning normalized). Keep error wrapping the same when
parseVersionNumber fails and return an explicit error if n < 1. Ensure
references: ParseSchemaVersion, parseVersionNumber, SchemaVersion, and Latest
are used as described.
🪄 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: d05e7484-5db8-4136-bf4e-96f94a252be2

📥 Commits

Reviewing files that changed from the base of the PR and between 65b8e63 and b38b809.

📒 Files selected for processing (8)
  • agent/go/internal/schema/schema.go
  • agent/go/internal/schema/schema_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/schema/schema_test.go
Comment thread agent/go/internal/step/regular_step.go Outdated
Comment thread agent/go/internal/step/regular_step.go
Comment thread agent/go/internal/step/step.go Outdated
Comment thread agent/go/internal/step/upgrade_step_test.go
Comment thread agent/go/internal/step/upgrade_step.go Outdated
Comment thread agent/go/internal/step/upgrade_step.go
@rice-riley rice-riley force-pushed the port-enums-and-steps branch from b38b809 to e4b3643 Compare June 9, 2026 03:53

@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

♻️ Duplicate comments (1)
agent/go/internal/step/regular_step.go (1)

35-40: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Preserve on_host default parity for direct RegularStep literals.

Line 39 uses OnHost bool, and Lines 136-139 explicitly skip defaulting it. That means RegularStep{Path: "foo.sh"}.Encode() emits "on_host":false, which diverges from Python’s default-true wire behavior and the parity objective. Please switch to an unset-capable representation (for example *bool + defaulting) or prevent direct literal construction so defaulting can be enforced uniformly.

Also applies to: 136-139

🤖 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/regular_step.go` around lines 35 - 40, The RegularStep
struct's OnHost bool causes direct literals like RegularStep{Path:"foo.sh"} to
encode "on_host": false breaking parity; change OnHost to a pointer (*bool) on
the struct (symbol: OnHost in type RegularStep) and update RegularStep.Encode
(and any defaulting logic tied to Idempotence) to treat nil as "default true"
when serializing, ensuring existing defaulting at lines handling default values
(previously skipping OnHost) still applies; alternatively, prevent direct
literal construction by making the struct unexported and providing a constructor
that sets the default, but prefer the *bool + default-in-Encode approach for
minimal disruption.
🤖 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/step/upgrade_step.go`:
- Around line 72-74: UpgradeStep.Encode currently returns the bare error from
u.Validate(), losing call-site context; change the return to wrap the validation
error with context (e.g., return nil, fmt.Errorf("upgrade step validation
failed: %w", err)) so callers see which step failed, and add/import fmt if not
present; refer to UpgradeStep.Encode and u.Validate when making the change.

---

Duplicate comments:
In `@agent/go/internal/step/regular_step.go`:
- Around line 35-40: The RegularStep struct's OnHost bool causes direct literals
like RegularStep{Path:"foo.sh"} to encode "on_host": false breaking parity;
change OnHost to a pointer (*bool) on the struct (symbol: OnHost in type
RegularStep) and update RegularStep.Encode (and any defaulting logic tied to
Idempotence) to treat nil as "default true" when serializing, ensuring existing
defaulting at lines handling default values (previously skipping OnHost) still
applies; alternatively, prevent direct literal construction by making the struct
unexported and providing a constructor that sets the default, but prefer the
*bool + default-in-Encode approach for minimal disruption.
🪄 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: 7d126f11-6e1b-47e9-9dd4-f97cc7875e1f

📥 Commits

Reviewing files that changed from the base of the PR and between b38b809 and e4b3643.

📒 Files selected for processing (8)
  • agent/go/internal/schema/schema.go
  • agent/go/internal/schema/schema_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/step/upgrade_step.go
@rice-riley rice-riley force-pushed the port-enums-and-steps branch from e4b3643 to bb3f44a Compare June 9, 2026 14:46

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

🤖 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/step/regular_step.go`:
- Around line 34-49: The exported RegularStep struct currently has OnHost bool
which serializes false for zero-value literals and breaks Python parity; change
OnHost to a pointer (*bool) with the json tag `on_host,omitempty`, update
NewRegularStep to initialize OnHost to pointer-to-true, and modify applyDefaults
(and any code referencing OnHost) to treat nil as “unset” and set the pointer to
true when applying defaults; update tests to use NewRegularStep (or set &true)
where they construct RegularStep literals so the field is omitted/round-trips
correctly.

In `@agent/go/internal/step/step_test.go`:
- Around line 224-236: The test for RegularStep zero-value should assert the
OnHost behavior to document the wire discrepancy: update the test (the It block
that constructs bare := RegularStep{Path: "foo.sh"} and calls bare.Encode() /
Decode()) to either assert that the encoded wire form contains on_host:false or
assert that after round-trip the decoded rs.OnHost is false; reference
RegularStep, Encode, Decode and the OnHost field so reviewers can locate and
verify the added assertion.
🪄 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: 06d0a09e-9432-4de3-b73e-2cc66821b069

📥 Commits

Reviewing files that changed from the base of the PR and between e4b3643 and bb3f44a.

📒 Files selected for processing (8)
  • agent/go/internal/schema/schema.go
  • agent/go/internal/schema/schema_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/step/regular_step.go
Comment thread agent/go/internal/step/step_test.go
Comment thread agent/go/internal/step/step.go Outdated
lockwobr
lockwobr previously approved these changes Jun 9, 2026

@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.

One thing that is not a blocker, we can come back later to make things more uniform once we get closer to parity.

* ci: publish keyless release attestations (#232) (#240)

* ci: publish keyless release attestations

* ci: drop nvcr release attestations

* ci: narrow release attestation identity

* ci: gate attestations to release tags

* docs: show immutable release subjects

* ci: attest helm chart releases

---------

Signed-off-by: AnouarMohamed <[email protected]>
Co-authored-by: Anouar Mohamed <[email protected]>

* chore(docs): update docs around release and location of helm chart (#237) (#239)

* fix(test): uninstall downgraded package before cleanup in downgrade-after-uninstall (#241)

The test left [email protected] freshly installed at chainsaw's automatic
CLEANUP, where the new delete-time uninstall finalizer (from #200) must
run uninstall pods on every node before releasing the CR. That exceeds
chainsaw's default cleanup window, causing "context deadline exceeded".

Add a final uninstall-v1 step that flips uninstall.apply=true on the
downgraded version and waits for nodeState to empty, so the CR has no
installed packages when cleanup deletes it.

Signed-off-by: Alex Yuskauskas <[email protected]>

* fix: deadlock in webhook controller when upgrading from old versions (#243)

* fix: close StageInterrupt trap + harden core e2e pool (#242)

* fix(test): harden core e2e pool against finalizer-cleanup races and improve diagnosability

Two changes across the six core-pool chainsaw tests:

1. Add `timeouts.cleanup: 120s` to every core-pool test. Chainsaw's
   default cleanup window (~30s) is too tight for the project's CR
   finalizer, which must uncordon nodes, remove labels/annotations,
   and GC package pods before releasing. Same failure mode that hit
   downgrade-after-uninstall (PR #241).

2. Add `catch:` blocks to `depends-on` and `simple-skyhook`, mirroring
   the pattern used by the other four core tests. When these tests
   flake, CI now captures the Node, Skyhook CR, and package Pods for
   diagnosis instead of failing without artifacts.

No assert logic changes — this is a foundation pass to remove a known
flake class and make future flakes diagnosable.

Signed-off-by: Alex Yuskauskas <[email protected]>

* fix(operator): close StageInterrupt trap when configUpdates signal decays

A package whose only interrupt is a `configInterrupts` entry (no top-level
`interrupt` block) could get stuck at `StageInterrupt/StateSkipped`
permanently when `Status.ConfigUpdates` cleared or never persisted (e.g.
due to a 409 on the spec patch). The state machine queried the dynamic
`HasInterrupt(config)` signal at four points, and once that signal
decayed to false, the package was untouchable: `ProgressSkipped`
wouldn't promote it, `NextStage` wouldn't advance it past Interrupt,
and `GetComplete`/`IsPackageComplete` wouldn't count it complete.

Decouple progression-past-Interrupt from the dynamic signal:

- `NextStage`: add `StageInterrupt → StagePostInterrupt` to the
  no-interrupt default map. The with-interrupt full-replacement map is
  preserved as-is — PR #200 deliberately omits `StageUninstall →
  StageApply` from it so with-interrupt uninstalls route via
  `StageUninstallInterrupt`; collapsing the maps would silently
  re-enable that transition.
- `ProgressSkipped`: drop the `HasInterrupt` gate. The only writer of
  `StateSkipped` at `StageInterrupt` is `ProcessInterrupt`'s
  budget-contention branch, which already decided the package needed
  an interrupt to schedule. Stage alone is sufficient.
- `GetComplete`, `IsPackageComplete`: treat `StagePostInterrupt` as
  unconditionally terminal. The only way to reach it is via the
  interrupt cycle; gating the terminal check on a signal that can
  decay is redundant with the entry gate and only becomes load-bearing
  when the trap fires.

Reproducer in `skyhook_types_test.go` exercises all three sites.

Surfaced by the `chainsaw/config-skyhook` "update while running" step,
which patches the spec mid-flight and concurrently triggers the
`HandleMigrations` and `processSkyhooksPerNode` 409 conflict paths —
those stretch convergence enough that ConfigUpdates can be lost
between the package reaching StageInterrupt and ProgressSkipped firing.

Signed-off-by: Alex Yuskauskas <[email protected]>

* refactor(operator): GetComplete delegates to IsPackageComplete

GetComplete and IsPackageComplete encoded the same terminal-state logic
twice. Have GetComplete iterate the spec packages and call
IsPackageComplete for each, and move the explanatory comment to
IsPackageComplete where the predicate lives.

Behavior preserved: the prior implementation iterated node state and
filtered to spec packages by name+version match; the new one iterates
spec packages and looks them up in node state by unique name (name|version).
Same set of packages either way.

Signed-off-by: Alex Yuskauskas <[email protected]>

---------

Signed-off-by: Alex Yuskauskas <[email protected]>

---------

Signed-off-by: AnouarMohamed <[email protected]>
Signed-off-by: Alex Yuskauskas <[email protected]>
Co-authored-by: Anouar Mohamed <[email protected]>
Co-authored-by: ayuskauskas <[email protected]>
@rice-riley rice-riley enabled auto-merge (squash) June 9, 2026 23:21

@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.

♻️ Duplicate comments (1)
agent/go/internal/step/regular_step.go (1)

34-49: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Zero-value RegularStep literal breaks Python wire parity for on_host.

RegularStep is exported, allowing direct construction: RegularStep{Path: "foo.sh"}. Because Go's bool zero-value is false and applyDefaults intentionally skips OnHost (lines 136-138), encoding a zero-value literal produces "on_host":false, violating the stated goal of "byte parity with Python" (where the default is on_host=true).

The documented constructor NewRegularStep seeds OnHost=true (line 124) and works correctly, but the exported struct allows the broken path. Several tests construct literals directly (e.g., regular_step_test.go lines 28, 34, etc.), though they don't verify OnHost behavior and may mask this discrepancy.

Mitigation options:

  1. Make RegularStep unexported (regularStep) to force use of NewRegularStep.
  2. Change OnHost to *bool so applyDefaults can detect unset and default to true.
  3. Document that direct literal construction is unsupported and add a runtime guard or lint rule.
🤖 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/regular_step.go` around lines 34 - 49, Change the
OnHost field to a pointer so unset literals don't serialize "on_host":false:
update RegularStep.OnHost from bool to *bool (and add `omitempty` to its json
tag), update NewRegularStep to allocate and set a true pointer, and modify
applyDefaults (and any callers) to treat nil as "unset" and set it to true when
defaulting; run and adjust tests (e.g., regular_step_test.go) to construct or
inspect the pointer/dereferenced value as needed.
🤖 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.

Duplicate comments:
In `@agent/go/internal/step/regular_step.go`:
- Around line 34-49: Change the OnHost field to a pointer so unset literals
don't serialize "on_host":false: update RegularStep.OnHost from bool to *bool
(and add `omitempty` to its json tag), update NewRegularStep to allocate and set
a true pointer, and modify applyDefaults (and any callers) to treat nil as
"unset" and set it to true when defaulting; run and adjust tests (e.g.,
regular_step_test.go) to construct or inspect the pointer/dereferenced value as
needed.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Enterprise

Run ID: d6689325-5d5a-44c1-809b-fe4db8347ebf

📥 Commits

Reviewing files that changed from the base of the PR and between bb3f44a and 91f8f6c.

📒 Files selected for processing (8)
  • agent/go/internal/schema/schema.go
  • agent/go/internal/schema/schema_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

@rice-riley rice-riley merged commit aae1166 into main Jun 9, 2026
11 checks passed
@rice-riley rice-riley deleted the port-enums-and-steps branch June 9, 2026 23:24
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]: Bootstrap agent/go/ module + port pure-data types

3 participants